@@ -4,21 +4,24 @@ use crate::anchor_management::registration::captcha::{
44use crate :: anchor_management:: registration:: rate_limit:: process_rate_limit;
55use crate :: anchor_management:: registration:: Base64 ;
66use crate :: anchor_management:: {
7- activity_bookkeeping, add_openid_credential, post_operation_bookkeeping, set_name,
7+ activity_bookkeeping, add_openid_credential_skip_checks, check_openid_credential_is_unique,
8+ post_operation_bookkeeping, set_name,
89} ;
910use crate :: state:: flow_states:: RegistrationFlowState ;
10- use crate :: storage:: anchor:: Device ;
11+ use crate :: storage:: anchor:: { Anchor , Device } ;
12+ use crate :: storage:: Storage ;
1113use crate :: { openid, state} ;
1214use candid:: Principal ;
1315use ic_cdk:: api:: time;
1416use ic_cdk:: caller;
17+ use ic_stable_structures:: Memory ;
1518use internet_identity_interface:: archive:: types:: { DeviceDataWithoutAlias , Operation } ;
1619use internet_identity_interface:: internet_identity:: types:: IdRegFinishError :: IdentityLimitReached ;
1720use internet_identity_interface:: internet_identity:: types:: {
1821 AuthorizationKey , CaptchaTrigger , CheckCaptchaArg , CheckCaptchaError , CreateIdentityData ,
19- DeviceData , DeviceWithUsage , IdRegFinishError , IdRegFinishResult , IdRegNextStepResult ,
20- IdRegStartError , IdentityNumber , OpenIDRegFinishArg , RegistrationFlowNextStep ,
21- StaticCaptchaTrigger ,
22+ DeviceData , DeviceWithUsage , IdRegFinishArg , IdRegFinishError , IdRegFinishResult ,
23+ IdRegNextStepResult , IdRegStartError , IdentityNumber , OpenIDRegFinishArg ,
24+ RegistrationFlowNextStep , StaticCaptchaTrigger ,
2225} ;
2326
2427impl RegistrationFlowState {
@@ -166,7 +169,9 @@ pub fn identity_registration_finish(
166169 } ) ;
167170 } ;
168171
169- let identity_number = create_identity ( & arg) ?;
172+ let now = time ( ) ;
173+
174+ let identity_number = create_identity ( & arg, now) ?;
170175
171176 // flow completed --> remove flow state
172177 state:: with_flow_states_mut ( |flow_states| flow_states. remove_registration_flow ( & caller) ) ;
@@ -192,14 +197,65 @@ pub fn identity_registration_finish(
192197 Ok ( IdRegFinishResult { identity_number } )
193198}
194199
195- fn create_identity ( arg : & CreateIdentityData ) -> Result < IdentityNumber , IdRegFinishError > {
196- let now = time ( ) ;
197- let Some ( mut identity) = state:: storage_borrow_mut ( |s| s. allocate_anchor ( now) ) else {
198- return Err ( IdentityLimitReached ) ;
199- } ;
200+ fn create_openid_credential_and_config (
201+ openid_registration_data : & OpenIDRegFinishArg ,
202+ ) -> Result < ( openid:: OpenIdCredential , String ) , IdRegFinishError > {
203+ let OpenIDRegFinishArg { jwt, salt, name : _ } = openid_registration_data;
200204
201- let operation = match & arg {
202- CreateIdentityData :: PubkeyAuthn ( id_reg_finish_arg) => {
205+ let ( openid_credential, openid_config_iss) = openid:: with_provider ( jwt, |provider| {
206+ Ok ( ( provider. verify ( jwt, salt) ?, provider. issuer ( ) ) )
207+ } ) ?;
208+
209+ Ok ( ( openid_credential, openid_config_iss) )
210+ }
211+
212+ #[ derive( Clone , Debug , Eq , PartialEq ) ]
213+ enum ValidatedCreateIdentityData {
214+ PubkeyAuthn ( IdRegFinishArg ) ,
215+ OpenID ( ValidatedOpenIDRegFinishArg ) ,
216+ }
217+
218+ #[ derive( Clone , Debug , Eq , PartialEq ) ]
219+ pub struct ValidatedOpenIDRegFinishArg {
220+ pub name : String ,
221+ pub credential : openid:: OpenIdCredential ,
222+ pub openid_config_iss : String ,
223+ }
224+
225+ fn validate_identity_data < M : Memory + Clone > (
226+ storage : & Storage < M > ,
227+ arg : & CreateIdentityData ,
228+ ) -> Result < ValidatedCreateIdentityData , IdRegFinishError > {
229+ match & arg {
230+ CreateIdentityData :: PubkeyAuthn ( arg) => {
231+ Ok ( ValidatedCreateIdentityData :: PubkeyAuthn ( arg. clone ( ) ) )
232+ }
233+ CreateIdentityData :: OpenID ( openid_registration_data) => {
234+ let ( credential, openid_config_iss) =
235+ create_openid_credential_and_config ( openid_registration_data) ?;
236+
237+ check_openid_credential_is_unique ( storage, & credential. key ( ) )
238+ . map_err ( |err| IdRegFinishError :: InvalidAuthnMethod ( err. to_string ( ) ) ) ?;
239+
240+ let name = openid_registration_data. name . clone ( ) ;
241+
242+ Ok ( ValidatedCreateIdentityData :: OpenID (
243+ ValidatedOpenIDRegFinishArg {
244+ name,
245+ credential,
246+ openid_config_iss,
247+ } ,
248+ ) )
249+ }
250+ }
251+ }
252+
253+ fn apply_identity_data (
254+ identity : & mut Anchor ,
255+ arg : ValidatedCreateIdentityData ,
256+ ) -> Result < Operation , IdRegFinishError > {
257+ match arg {
258+ ValidatedCreateIdentityData :: PubkeyAuthn ( id_reg_finish_arg) => {
203259 let name = id_reg_finish_arg. name . clone ( ) ;
204260 let device = DeviceWithUsage :: try_from ( id_reg_finish_arg. authn_method . clone ( ) )
205261 . map ( |device| Device :: from ( DeviceData :: from ( device) ) )
@@ -208,49 +264,130 @@ fn create_identity(arg: &CreateIdentityData) -> Result<IdentityNumber, IdRegFini
208264 identity
209265 . add_device ( device. clone ( ) )
210266 . map_err ( |err| IdRegFinishError :: InvalidAuthnMethod ( err. to_string ( ) ) ) ?;
211- set_name ( & mut identity, name)
267+ set_name ( identity, name)
212268 . map_err ( |err| IdRegFinishError :: StorageError ( err. to_string ( ) ) ) ?;
213269 activity_bookkeeping (
214- & mut identity,
270+ identity,
215271 & AuthorizationKey :: DeviceKey ( device. pubkey . clone ( ) ) ,
216272 ) ;
217273
218- Operation :: RegisterAnchor {
274+ Ok ( Operation :: RegisterAnchor {
219275 device : DeviceDataWithoutAlias :: from ( device) ,
220- }
276+ } )
221277 }
222- CreateIdentityData :: OpenID ( openid_registration_data) => {
223- let OpenIDRegFinishArg { jwt, salt, name } = openid_registration_data;
224- let ( openid_credential, openid_config_iss) = openid:: with_provider ( jwt, |provider| {
225- Ok ( ( provider. verify ( jwt, salt) ?, provider. issuer ( ) ) )
226- } ) ?;
227- add_openid_credential ( & mut identity, openid_credential. clone ( ) )
278+ ValidatedCreateIdentityData :: OpenID ( ValidatedOpenIDRegFinishArg {
279+ name,
280+ credential,
281+ openid_config_iss,
282+ } ) => {
283+ let key = credential. key ( ) ;
284+ let iss = openid_config_iss. clone ( ) ;
285+
286+ add_openid_credential_skip_checks ( identity, credential)
228287 . map_err ( |err| IdRegFinishError :: InvalidAuthnMethod ( err. to_string ( ) ) ) ?;
229- set_name ( & mut identity, Some ( name. clone ( ) ) )
288+
289+ set_name ( identity, Some ( name) )
230290 . map_err ( |err| IdRegFinishError :: StorageError ( err. to_string ( ) ) ) ?;
291+
231292 activity_bookkeeping (
232- & mut identity,
233- & AuthorizationKey :: OpenIdCredentialKey ( (
234- openid_credential. key ( ) ,
235- Some ( openid_config_iss) ,
236- ) ) ,
293+ identity,
294+ & AuthorizationKey :: OpenIdCredentialKey ( ( key, Some ( openid_config_iss) ) ) ,
237295 ) ;
238296
239- Operation :: RegisterAnchorWithOpenIdCredential {
240- iss : openid_credential. iss ,
241- }
297+ Ok ( Operation :: RegisterAnchorWithOpenIdCredential { iss } )
242298 }
243- } ;
299+ }
300+ }
244301
245- let identity_number = identity. anchor_number ( ) ;
302+ fn create_identity ( arg : & CreateIdentityData , now : u64 ) -> Result < IdentityNumber , IdRegFinishError > {
303+ let ( operation, identity_number) : ( Operation , u64 ) = state:: storage_borrow_mut ( |storage| {
304+ let arg = validate_identity_data ( storage, arg) ?;
246305
247- state:: storage_borrow_mut ( |s| {
248- s. registration_rates . new_registration ( ) ;
249- s. create ( identity)
250- } )
251- . map_err ( |err| IdRegFinishError :: StorageError ( err. to_string ( ) ) ) ?;
306+ let ( operation, identity) =
307+ storage. allocate_anchor_safe ( now, |identity : Option < Anchor > | {
308+ let Some ( mut identity) = identity else {
309+ return Err ( IdentityLimitReached ) ;
310+ } ;
311+
312+ let operation = apply_identity_data ( & mut identity, arg) ?;
313+
314+ Ok ( ( operation, identity) )
315+ } ) ?;
316+
317+ let identity_number = identity. anchor_number ( ) ;
318+
319+ storage. registration_rates . new_registration ( ) ;
320+ storage
321+ . create ( identity)
322+ . map_err ( |err| IdRegFinishError :: StorageError ( err. to_string ( ) ) ) ?;
323+
324+ Ok :: < _ , IdRegFinishError > ( ( operation, identity_number) )
325+ } ) ?;
252326
327+ // TODO: propagate the `now` timestamp from above to here to avoid `time()` call.
253328 post_operation_bookkeeping ( identity_number, operation) ;
254329
255330 Ok ( identity_number)
256331}
332+
333+ #[ cfg( test) ]
334+ mod create_identity_tests {
335+ use super :: * ;
336+ use crate :: state:: { storage_borrow, storage_replace} ;
337+ use crate :: storage:: Storage ;
338+ use ic_stable_structures:: VectorMemory ;
339+ use internet_identity_interface:: internet_identity:: types:: {
340+ AuthnMethodData , DeviceData , DeviceProtection , KeyType , Purpose ,
341+ } ;
342+
343+ #[ test]
344+ fn create_identity_failure_does_not_leave_dangling_anchor_allocation ( ) {
345+ storage_replace ( Storage :: new ( ( 0 , 10000 ) , VectorMemory :: default ( ) ) ) ;
346+
347+ // Record initial anchor count
348+ let initial_anchor_count = storage_borrow ( |storage| storage. anchor_count ( ) ) ;
349+
350+ // Create identity data with an extremely long name that will cause failure
351+ let very_long_name = "a" . repeat ( 10_000 ) ; // Assuming this exceeds the name length limit
352+ let create_identity_data = CreateIdentityData :: PubkeyAuthn (
353+ internet_identity_interface:: internet_identity:: types:: IdRegFinishArg {
354+ authn_method : AuthnMethodData :: from ( DeviceData {
355+ pubkey : vec ! [ 1 , 2 , 3 ] . into ( ) ,
356+ alias : "test_device" . to_string ( ) ,
357+ credential_id : Some ( vec ! [ 4 , 5 , 6 ] . into ( ) ) ,
358+ purpose : Purpose :: Authentication ,
359+ key_type : KeyType :: Unknown ,
360+ protection : DeviceProtection :: Unprotected ,
361+ origin : None ,
362+ metadata : None ,
363+ } ) ,
364+ name : Some ( very_long_name) ,
365+ } ,
366+ ) ;
367+
368+ // Attempt to create identity - this should fail
369+ let result = create_identity ( & create_identity_data, 111 ) ;
370+
371+ // Verify that the creation failed
372+ assert ! ( result. is_err( ) ) ;
373+
374+ // Verify that the anchor count hasn't changed - no dangling allocation
375+ let final_anchor_count = storage_borrow ( |storage| storage. anchor_count ( ) ) ;
376+ assert_eq ! (
377+ initial_anchor_count, final_anchor_count,
378+ "Anchor count should remain unchanged after failed identity creation"
379+ ) ;
380+
381+ // Verify that no anchor was actually created by trying to read the next anchor number
382+ let expected_next_anchor = storage_borrow ( |storage| {
383+ let ( id_range_lo, _) = storage. assigned_anchor_number_range ( ) ;
384+ id_range_lo + final_anchor_count as u64
385+ } ) ;
386+
387+ let read_result = storage_borrow ( |storage| storage. read ( expected_next_anchor) ) ;
388+ assert ! (
389+ read_result. is_err( ) ,
390+ "No anchor should exist at the next anchor number after failed creation"
391+ ) ;
392+ }
393+ }
0 commit comments