@@ -381,6 +381,10 @@ static int s_aws_secitem_add_certificate_to_keychain(
381381 CFDictionaryAddValue (add_attributes , kSecAttrSerialNumber , serial_data );
382382 CFDictionaryAddValue (add_attributes , kSecAttrLabel , label );
383383 CFDictionaryAddValue (add_attributes , kSecValueRef , cert_ref );
384+ #ifdef AWS_SECITEM_FILEBASED_KEYCHAIN
385+ /* Target file-based keychain instead of data protection keychain. */
386+ CFDictionaryAddValue (add_attributes , kSecUseDataProtectionKeychain , kCFBooleanFalse );
387+ #endif
384388
385389 // Initial attempt to add certificate to keychain.
386390 status = SecItemAdd (add_attributes , NULL );
@@ -435,6 +439,10 @@ static int s_aws_secitem_add_certificate_to_keychain(
435439 CFDictionaryCreateMutable (cf_alloc , 0 , & kCFTypeDictionaryKeyCallBacks , & kCFTypeDictionaryValueCallBacks );
436440 CFDictionaryAddValue (delete_query , kSecClass , kSecClassCertificate );
437441 CFDictionaryAddValue (delete_query , kSecAttrSerialNumber , serial_data );
442+ #ifdef AWS_SECITEM_FILEBASED_KEYCHAIN
443+ /* Target file-based keychain instead of data protection keychain. */
444+ CFDictionaryAddValue (delete_query , kSecUseDataProtectionKeychain , kCFBooleanFalse );
445+ #endif
438446
439447 // delete the existing certificate from keychain
440448 status = SecItemDelete (delete_query );
@@ -483,6 +491,10 @@ static int s_aws_secitem_add_private_key_to_keychain(
483491 CFDictionaryAddValue (add_attributes , kSecAttrApplicationLabel , application_label );
484492 CFDictionaryAddValue (add_attributes , kSecAttrLabel , label );
485493 CFDictionaryAddValue (add_attributes , kSecValueRef , key_ref );
494+ #ifdef AWS_SECITEM_FILEBASED_KEYCHAIN
495+ /* Target file-based keychain instead of data protection keychain. */
496+ CFDictionaryAddValue (add_attributes , kSecUseDataProtectionKeychain , kCFBooleanFalse );
497+ #endif
486498
487499 // Initial attempt to add private key to keychain.
488500 status = SecItemAdd (add_attributes , NULL );
@@ -535,7 +547,10 @@ static int s_aws_secitem_add_private_key_to_keychain(
535547 CFDictionaryAddValue (delete_query , kSecClass , kSecClassKey );
536548 CFDictionaryAddValue (delete_query , kSecAttrKeyClass , kSecAttrKeyClassPrivate );
537549 CFDictionaryAddValue (delete_query , kSecAttrApplicationLabel , application_label );
538-
550+ #ifdef AWS_SECITEM_FILEBASED_KEYCHAIN
551+ /* Target file-based keychain instead of data protection keychain. */
552+ CFDictionaryAddValue (delete_query , kSecUseDataProtectionKeychain , kCFBooleanFalse );
553+ #endif
539554 // delete the existing private key from keychain
540555 status = SecItemDelete (delete_query );
541556 if (status != errSecSuccess ) {
@@ -565,12 +580,23 @@ static int s_aws_secitem_add_private_key_to_keychain(
565580 return result ;
566581}
567582
568- static int s_aws_secitem_get_identity (CFAllocatorRef cf_alloc , CFDataRef serial_data , sec_identity_t * out_identity ) {
583+ static bool s_compare_serial_numbers (CFDataRef a , CFDataRef b ) {
584+ return CFDataGetLength (a ) == CFDataGetLength (b ) &&
585+ memcmp (CFDataGetBytePtr (a ), CFDataGetBytePtr (b ), CFDataGetLength (a )) == 0 ;
586+ }
587+
588+ static int s_aws_secitem_get_identity (
589+ CFAllocatorRef cf_alloc ,
590+ CFDataRef serial_data ,
591+ SecCertificateRef cert_ref ,
592+ sec_identity_t * out_identity ) {
593+
594+ (void )cert_ref ;
569595
570596 int result = AWS_OP_ERR ;
571597 OSStatus status ;
572598 CFMutableDictionaryRef search_query = NULL ;
573- SecIdentityRef sec_identity_ref = NULL ;
599+ CFArrayRef sec_identity_array = NULL ;
574600
575601 /*
576602 * SecItem identity is created when a certificate matches a private key in the keychain.
@@ -584,34 +610,84 @@ static int s_aws_secitem_get_identity(CFAllocatorRef cf_alloc, CFDataRef serial_
584610 CFDictionaryAddValue (search_query , kSecAttrSerialNumber , serial_data );
585611 CFDictionaryAddValue (search_query , kSecReturnRef , kCFBooleanTrue );
586612
613+ #ifdef AWS_SECITEM_FILEBASED_KEYCHAIN
614+ /* Target file-based keychain instead of data protection keychain. */
615+ CFDictionaryAddValue (search_query , kSecUseDataProtectionKeychain , kCFBooleanFalse );
616+ /* The kSecAttrSerialNumber filter attribute does not work for kSecClassIdentity when SecItem targets file-based
617+ * keychain. So, use additional filtering by a certificate provided by a user. */
618+ CFArrayRef cert_filter = CFArrayCreate (cf_alloc , (const void * * )& cert_ref , 1L , & kCFTypeArrayCallBacks );
619+ CFDictionaryAddValue (search_query , kSecMatchItemList , cert_filter );
620+ #endif
621+
622+ /* Though the kSecAttrSerialNumber and kSecMatchItemList attributes filter out unmatching identities, request
623+ * SecItemCopyMatching to return all results. */
624+ CFDictionaryAddValue (search_query , kSecMatchLimit , kSecMatchLimitAll );
625+
587626 /*
588627 * Copied or created CF items must have CFRelease called on them or you leak memory. This identity needs to
589628 * have CFRelease called on it at some point or it will leak.
590629 */
591- status = SecItemCopyMatching (search_query , (CFTypeRef * )& sec_identity_ref );
630+ status = SecItemCopyMatching (search_query , (CFTypeRef * )& sec_identity_array );
592631
593632 if (status != errSecSuccess ) {
594633 AWS_LOGF_ERROR (AWS_LS_IO_PKI , "SecItemCopyMatching identity failed with OSStatus %d" , (int )status );
595634 aws_raise_error (AWS_ERROR_SYS_CALL_FAILURE );
596635 goto done ;
597636 }
598637
599- * out_identity = sec_identity_create (sec_identity_ref );
600- if (* out_identity == NULL ) {
601- AWS_LOGF_ERROR (
602- AWS_LS_IO_PKI , "sec_identity_create failed to create a sec_identity_t from provided SecIdentityRef." );
603- aws_raise_error (AWS_ERROR_SYS_CALL_FAILURE );
604- goto done ;
638+ CFIndex identity_num = CFArrayGetCount (sec_identity_array );
639+ AWS_LOGF_DEBUG (AWS_LS_IO_PKI , "Found %d identities" , (int )identity_num );
640+
641+ /* With the kSecAttrSerialNumber and kSecMatchItemList filters in place, the following additional serial number
642+ * verification is NOT needed. However, I'll keep it for now to be on the safe side.
643+ * NOTE: Only public data is processed here, i.e. any logged user already has access to it without any additional
644+ * verification. */
645+ for (CFIndex i = 0 ; i < identity_num ; i ++ ) {
646+ /* The identity object here is basically two references: one to a cert and one to a private key. No actual
647+ * data is retrieved from keychain yet. We can use special functions provided by Security framework to get them:
648+ * SecIdentityCopyCertificate and SecIdentityCopyPrivateKey.
649+ * Certificate data can be obtained with no additional verification since it's public. Private key data can be
650+ * obtained only if the user has access to the keychain (i.e. either provide password into keychain's popup or
651+ * add the app to a list of allowed in the Keychain Access app).
652+ * Since we process only certificate here, no user verification will be requested in this function. Later,
653+ * during establishing a TLS connection, user verification will be done when Network framework will try to retrieve
654+ * private key data from the provided identity. That part remains the same. */
655+ const SecIdentityRef sec_identity_ref = (const SecIdentityRef )CFArrayGetValueAtIndex (sec_identity_array , i );
656+
657+ SecCertificateRef found_cert = NULL ;
658+ OSStatus copy_cert_status = SecIdentityCopyCertificate (sec_identity_ref , & found_cert );
659+ if (copy_cert_status != errSecSuccess ) {
660+ AWS_LOGF_ERROR (AWS_LS_IO_PKI , "SecIdentityCopyCertificate failed with OSStatus %d" , (int )copy_cert_status );
661+ aws_raise_error (AWS_ERROR_SYS_CALL_FAILURE );
662+ goto done ;
663+ }
664+
665+ CFDataRef found_cert_serial_data = SecCertificateCopySerialNumberData (found_cert , NULL );
666+
667+ if (s_compare_serial_numbers (serial_data , found_cert_serial_data )) {
668+ AWS_LOGF_TRACE (AWS_LS_IO_PKI , "Found a matching identity" );
669+ * out_identity = sec_identity_create (sec_identity_ref );
670+ if (* out_identity == NULL ) {
671+ AWS_LOGF_ERROR (
672+ AWS_LS_IO_PKI ,
673+ "sec_identity_create failed to create a sec_identity_t from provided SecIdentityRef." );
674+ aws_raise_error (AWS_ERROR_SYS_CALL_FAILURE );
675+ goto done ;
676+ }
677+
678+ break ;
679+ }
605680 }
606681
607682 AWS_LOGF_INFO (AWS_LS_IO_PKI , "static: Successfully retrieved identity from keychain." );
608-
609683 result = AWS_OP_SUCCESS ;
610684
611685done :
612686 // cleanup
613687 aws_cf_release (search_query );
614- aws_cf_release (sec_identity_ref );
688+ aws_cf_release (cert_filter );
689+ // TODO Release elements.
690+ aws_cf_release (sec_identity_array );
615691
616692 return result ;
617693}
@@ -774,6 +850,11 @@ int aws_secitem_import_cert_and_key(
774850 CFDictionaryCreateMutable (cf_alloc , 0 , & kCFTypeDictionaryKeyCallBacks , & kCFTypeDictionaryValueCallBacks );
775851 CFDictionaryAddValue (key_attributes , kSecAttrKeyClass , kSecAttrKeyClassPrivate );
776852 CFDictionaryAddValue (key_attributes , kSecAttrKeyType , key_type );
853+ #ifdef AWS_SECITEM_FILEBASED_KEYCHAIN
854+ /* Target file-based keychain instead of data protection keychain. */
855+ CFDictionaryAddValue (key_attributes , kSecUseDataProtectionKeychain , kCFBooleanFalse );
856+ #endif
857+
777858 key_ref = SecKeyCreateWithData (key_data , key_attributes , & error );
778859
779860 // Get the hash of the public key stored within the private key by extracting it from the key_ref's attributes
@@ -808,7 +889,7 @@ int aws_secitem_import_cert_and_key(
808889 goto done ;
809890 }
810891
811- if (s_aws_secitem_get_identity (cf_alloc , cert_serial_data , secitem_identity )) {
892+ if (s_aws_secitem_get_identity (cf_alloc , cert_serial_data , cert_ref , secitem_identity )) {
812893 goto done ;
813894 }
814895
0 commit comments