File: darwin_pki_utils.c

package info (click to toggle)
aws-crt-python 0.28.4%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 78,428 kB
  • sloc: ansic: 437,955; python: 27,657; makefile: 5,855; sh: 4,289; ruby: 208; java: 82; perl: 73; cpp: 25; xml: 11
file content (943 lines) | stat: -rw-r--r-- 37,870 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
/**
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0.
 */
#include <aws/io/private/pki_utils.h>

#include <aws/common/mutex.h>
#include <aws/common/string.h>
#include <aws/io/logging.h>
#include <aws/io/pem.h>
#include <aws/io/tls_channel_handler.h>

#include <Security/SecCertificate.h>
#include <Security/SecKey.h>
#include <Security/Security.h>

/* SecureTransport is not thread-safe during identity import */
/* https://developer.apple.com/documentation/security/certificate_key_and_trust_services/working_with_concurrency */
static struct aws_mutex s_sec_mutex = AWS_MUTEX_INIT;

#if !defined(AWS_OS_IOS)

/*
 * Helper function to import ECC private key in PEM format into `import_keychain`. Return
 * AWS_OP_SUCCESS if successfully imported a private key or find a duplicate key in the
 * `import_keychain`, otherwise return AWS_OP_ERR.
 * `private_key`: UTF-8 key data in PEM format. If the key file contains multiple key sections,
 * the function will only import the first valid key.
 * `import_keychain`: The keychain to be imported to. `import_keychain` should not be NULL.
 */
int aws_import_ecc_key_into_keychain(
    struct aws_allocator *alloc,
    CFAllocatorRef cf_alloc,
    const struct aws_byte_cursor *private_key,
    SecKeychainRef import_keychain) {
    // Ensure imported_keychain is not NULL
    AWS_PRECONDITION(import_keychain != NULL);
    AWS_PRECONDITION(private_key != NULL);

    int result = AWS_OP_ERR;
    struct aws_array_list decoded_key_buffer_list;

    /* Decode PEM format file to DER format */
    if (aws_pem_objects_init_from_file_contents(&decoded_key_buffer_list, alloc, *private_key)) {
        AWS_LOGF_ERROR(AWS_LS_IO_PKI, "static: Failed to decode PEM private key to DER format.");
        goto ecc_import_cleanup;
    }
    AWS_ASSERT(aws_array_list_is_valid(&decoded_key_buffer_list));

    // A PEM file could contains multiple PEM data section. Try importing each PEM section until find the first
    // succeed key.
    for (size_t index = 0; index < aws_array_list_length(&decoded_key_buffer_list); index++) {
        struct aws_pem_object *pem_object_ptr = NULL;
        /* We only check the first pem section. Currently, we dont support key with multiple pem section. */
        aws_array_list_get_at_ptr(&decoded_key_buffer_list, (void **)&pem_object_ptr, index);
        AWS_ASSERT(pem_object_ptr);
        CFDataRef key_data = CFDataCreate(cf_alloc, pem_object_ptr->data.buffer, pem_object_ptr->data.len);
        if (!key_data) {
            AWS_LOGF_ERROR(AWS_LS_IO_PKI, "static: error in creating ECC key data system call.");
            continue;
        }

        /* Import ECC key data into keychain. */
        SecExternalFormat format = kSecFormatOpenSSL;
        SecExternalItemType item_type = kSecItemTypePrivateKey;
        SecItemImportExportKeyParameters import_params;
        AWS_ZERO_STRUCT(import_params);
        import_params.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
        import_params.passphrase = CFSTR("");

        OSStatus key_status =
            SecItemImport(key_data, NULL, &format, &item_type, 0, &import_params, import_keychain, NULL);

        /* Clean up key buffer */
        CFRelease(key_data);

        // As long as we found an imported key, ignore the rest of keys
        if (key_status == errSecSuccess || key_status == errSecDuplicateItem) {
            result = AWS_OP_SUCCESS;
            break;
        } else {
            // Log the error code for key importing
            AWS_LOGF_ERROR(AWS_LS_IO_PKI, "static: error importing ECC private key with OSStatus %d", (int)key_status);
        }
    }

ecc_import_cleanup:
    // Zero out the array list and release it
    aws_pem_objects_clean_up(&decoded_key_buffer_list);
    return result;
}

int aws_import_public_and_private_keys_to_identity(
    struct aws_allocator *alloc,
    CFAllocatorRef cf_alloc,
    const struct aws_byte_cursor *public_cert_chain,
    const struct aws_byte_cursor *private_key,
    CFArrayRef *identity,
    const struct aws_string *keychain_path) {
    AWS_PRECONDITION(public_cert_chain != NULL);
    AWS_PRECONDITION(private_key != NULL);

    int result = AWS_OP_ERR;

    CFDataRef cert_data = NULL;
    CFDataRef key_data = NULL;

    CFArrayRef cert_import_output = NULL;
    CFArrayRef key_import_output = NULL;
    SecExternalFormat format = kSecFormatUnknown;
    SecExternalItemType item_type = kSecItemTypeCertificate;

    SecItemImportExportKeyParameters import_params;
    AWS_ZERO_STRUCT(import_params);
    import_params.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
    import_params.passphrase = CFSTR("");

    struct aws_array_list cert_chain_list;
    AWS_ZERO_STRUCT(cert_chain_list);
    CFDataRef root_cert_data = NULL;
    SecCertificateRef certificate_ref = NULL;
    SecKeychainRef import_keychain = NULL;

    cert_data = CFDataCreate(cf_alloc, public_cert_chain->ptr, public_cert_chain->len);
    if (!cert_data) {
        AWS_LOGF_ERROR(AWS_LS_IO_PKI, "static: failed creating public cert chain data.");
        result = aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE);
        goto done;
    }

    key_data = CFDataCreate(cf_alloc, private_key->ptr, private_key->len);
    if (!key_data) {
        AWS_LOGF_ERROR(AWS_LS_IO_PKI, "static: failed creating private key data.");
        result = aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE);
        goto done;
    }

#    pragma clang diagnostic push
#    pragma clang diagnostic ignored "-Wdeprecated-declarations"
    /* SecKeychain functions are marked as deprecated.
     * Disable compiler warnings for now, but consider removing support for keychain altogether */

    if (keychain_path) {
        OSStatus keychain_status = SecKeychainOpen(aws_string_c_str(keychain_path), &import_keychain);
        if (keychain_status != errSecSuccess) {
            AWS_LOGF_ERROR(
                AWS_LS_IO_PKI,
                "static: error opening keychain \"%s\" with OSStatus %d",
                aws_string_c_str(keychain_path),
                keychain_status);
            result = aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE);
            goto done;
        }
        keychain_status = SecKeychainUnlock(import_keychain, 0, "", true);
        if (keychain_status != errSecSuccess) {
            AWS_LOGF_ERROR(
                AWS_LS_IO_PKI,
                "static: error unlocking keychain \"%s\" with OSStatus %d",
                aws_string_c_str(keychain_path),
                keychain_status);
            result = aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE);
            goto done;
        }
    } else {
        OSStatus keychain_status = SecKeychainCopyDefault(&import_keychain);
        if (keychain_status != errSecSuccess) {
            AWS_LOGF_ERROR(
                AWS_LS_IO_PKI, "static: error opening the default keychain with OSStatus %d", keychain_status);
            result = aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE);
            goto done;
        }
    }

#    pragma clang diagnostic pop

    aws_mutex_lock(&s_sec_mutex);

    /* import certificate */
    OSStatus cert_status =
        SecItemImport(cert_data, NULL, &format, &item_type, 0, &import_params, import_keychain, &cert_import_output);

    /* import private key */
    format = kSecFormatUnknown;
    item_type = kSecItemTypePrivateKey;
    OSStatus key_status =
        SecItemImport(key_data, NULL, &format, &item_type, 0, &import_params, import_keychain, &key_import_output);

    if (cert_status != errSecSuccess && cert_status != errSecDuplicateItem) {
        AWS_LOGF_ERROR(AWS_LS_IO_PKI, "static: error importing certificate with OSStatus %d", (int)cert_status);
        result = aws_raise_error(AWS_IO_FILE_VALIDATION_FAILURE);
        goto done;
    }

    /*
     * If the key format is unknown, we tried to decode the key into DER format import it.
     * The PEM file might contains multiple key sections, we will only add the first succeed key into the keychain.
     */
    if (key_status == errSecUnknownFormat) {
        AWS_LOGF_TRACE(AWS_LS_IO_PKI, "static: error reading private key format, try ECC key format.");
        if (aws_import_ecc_key_into_keychain(alloc, cf_alloc, private_key, import_keychain)) {
            result = aws_raise_error(AWS_IO_FILE_VALIDATION_FAILURE);
            goto done;
        }
    } else if (key_status != errSecSuccess && key_status != errSecDuplicateItem) {
        AWS_LOGF_ERROR(AWS_LS_IO_PKI, "static: error importing private key with OSStatus %d", (int)key_status);
        result = aws_raise_error(AWS_IO_FILE_VALIDATION_FAILURE);
        goto done;
    }

    /* if it's already there, just convert this over to a cert and then let the keychain give it back to us. */
    if (cert_status == errSecDuplicateItem) {
        /* The text for this log is also in the README for each CRT and v2 IoT SDK.  If changed, please also change
         * where it is referenced. */
        AWS_LOGF_INFO(
            AWS_LS_IO_PKI,
            "static: certificate has an existing certificate-key pair that was previously imported into the Keychain.  "
            "Using key from Keychain instead of the one provided.");
        if (aws_pem_objects_init_from_file_contents(&cert_chain_list, alloc, *public_cert_chain)) {
            AWS_LOGF_ERROR(AWS_LS_IO_PKI, "static: decoding certificate PEM failed.");
            result = AWS_OP_ERR;
            goto done;
        }

        struct aws_pem_object *root_cert_ptr = NULL;
        aws_array_list_get_at_ptr(&cert_chain_list, (void **)&root_cert_ptr, 0);
        AWS_ASSERT(root_cert_ptr);
        root_cert_data = CFDataCreate(cf_alloc, root_cert_ptr->data.buffer, root_cert_ptr->data.len);
        if (!root_cert_data) {
            AWS_LOGF_ERROR(AWS_LS_IO_PKI, "static: failed creating root cert data.");
            result = aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE);
            goto done;
        }

        certificate_ref = SecCertificateCreateWithData(cf_alloc, root_cert_data);
        if (!certificate_ref) {
            AWS_LOGF_ERROR(AWS_LS_IO_PKI, "static: failed to create certificate.");
            result = aws_raise_error(AWS_IO_FILE_VALIDATION_FAILURE);
            goto done;
        }
    } else {
        certificate_ref = (SecCertificateRef)CFArrayGetValueAtIndex(cert_import_output, 0);
        /* SecCertificateCreateWithData returns an object with +1 retain, so we need to match that behavior here */
        CFRetain(certificate_ref);
    }

    /* we got a cert one way or the other, create the identity and return it */
    AWS_ASSERT(certificate_ref);
    SecIdentityRef identity_output;
    OSStatus status = SecIdentityCreateWithCertificate(import_keychain, certificate_ref, &identity_output);
    if (status != errSecSuccess) {
        AWS_LOGF_ERROR(AWS_LS_IO_PKI, "static: error creating identity with OSStatus %d", key_status);
        result = aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE);
        goto done;
    }

    CFTypeRef certs[] = {identity_output};
    *identity = CFArrayCreate(cf_alloc, (const void **)certs, 1L, &kCFTypeArrayCallBacks);
    result = AWS_OP_SUCCESS;

done:
    aws_mutex_unlock(&s_sec_mutex);
    if (certificate_ref) {
        CFRelease(certificate_ref);
    }
    if (root_cert_data) {
        CFRelease(root_cert_data);
    }
    if (cert_import_output) {
        CFRelease(cert_import_output);
    }
    if (key_import_output) {
        CFRelease(key_import_output);
    }
    if (import_keychain) {
        CFRelease(import_keychain);
    }
    if (cert_data) {
        CFRelease(cert_data);
    }
    if (key_data) {
        CFRelease(key_data);
    }
    aws_pem_objects_clean_up(&cert_chain_list);

    return result;
}

#else /* !AWS_OS_IOS */

int aws_import_public_and_private_keys_to_identity(
    struct aws_allocator *alloc,
    CFAllocatorRef cf_alloc,
    const struct aws_byte_cursor *public_cert_chain,
    const struct aws_byte_cursor *private_key,
    CFArrayRef *identity,
    const struct aws_string *keychain_path) {
    (void)alloc;
    (void)cf_alloc;
    (void)public_cert_chain;
    (void)private_key;
    (void)identity;
    (void)keychain_path;
    /* This should not be reached when using iOS */
    AWS_FATAL_ASSERT(false);
}

#endif

int aws_import_pkcs12_to_identity(
    CFAllocatorRef cf_alloc,
    const struct aws_byte_cursor *pkcs12_cursor,
    const struct aws_byte_cursor *password,
    CFArrayRef *identity) {
    CFDataRef pkcs12_data = CFDataCreate(cf_alloc, pkcs12_cursor->ptr, pkcs12_cursor->len);
    CFArrayRef items = NULL;

    CFMutableDictionaryRef dictionary = CFDictionaryCreateMutable(cf_alloc, 0, NULL, NULL);

    CFStringRef password_ref = CFSTR("");

    if (password->len) {
        password_ref = CFStringCreateWithBytes(cf_alloc, password->ptr, password->len, kCFStringEncodingUTF8, false);
    }

    CFDictionaryAddValue(dictionary, kSecImportExportPassphrase, password_ref);

    aws_mutex_lock(&s_sec_mutex);
    OSStatus status = SecPKCS12Import(pkcs12_data, dictionary, &items);
    aws_mutex_unlock(&s_sec_mutex);
    CFRelease(pkcs12_data);

    if (password_ref) {
        CFRelease(password_ref);
    }

    CFRelease(dictionary);

    if (status == errSecSuccess) {
        CFTypeRef item = (CFTypeRef)CFArrayGetValueAtIndex(items, 0);

        CFTypeRef identity_ref = (CFTypeRef)CFDictionaryGetValue((CFDictionaryRef)item, kSecImportItemIdentity);
        if (identity_ref) {
            *identity = CFArrayCreate(cf_alloc, &identity_ref, 1L, &kCFTypeArrayCallBacks);
        }

        CFRelease(items);
        return AWS_OP_SUCCESS;
    }

    AWS_LOGF_ERROR(AWS_LS_IO_PKI, "static: error importing pkcs#12 certificate OSStatus %d", (int)status);

    return AWS_OP_ERR;
}

/*
 * Apple Network framework and SecItem API's use of the data protection keychain is currently only implemented
 * for iOS and tvOS. We may add support for MacOS at a later date.
 */

void aws_cf_release(CFTypeRef obj) {
    if (obj != NULL) {
        CFRelease(obj);
    }
}

static int s_aws_secitem_add_certificate_to_keychain(
    CFAllocatorRef cf_alloc,
    SecCertificateRef cert_ref,
    CFDataRef serial_data,
    CFStringRef label) {

    int result = AWS_OP_ERR;
    OSStatus status;

    CFMutableDictionaryRef add_attributes = NULL;
    CFMutableDictionaryRef delete_query = NULL;

    add_attributes =
        CFDictionaryCreateMutable(cf_alloc, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
    CFDictionaryAddValue(add_attributes, kSecClass, kSecClassCertificate);
    CFDictionaryAddValue(add_attributes, kSecAttrSerialNumber, serial_data);
    CFDictionaryAddValue(add_attributes, kSecAttrLabel, label);
    CFDictionaryAddValue(add_attributes, kSecValueRef, cert_ref);

    // Initial attempt to add certificate to keychain.
    status = SecItemAdd(add_attributes, NULL);

    // A duplicate item is handled. All other errors are unhandled.
    if (status != errSecSuccess && status != errSecDuplicateItem) {
        switch (status) {
            case errSecMissingEntitlement:
                AWS_LOGF_ERROR(
                    AWS_LS_IO_PKI,
                    "SecItemAdd certificate failed with OSStatus %d : errSecMissingEntitlement. The process attempting "
                    "to access the keychain is missing the necessary entitlements.",
                    (int)status);
                break;
            default:
                AWS_LOGF_ERROR(AWS_LS_IO_PKI, "SecItemAdd certificate failed with OSStatus %d", (int)status);
                aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE);
                break;
        }
        goto done;
    }

    /* A duplicate item error indicates the certificate already exists in the keychain. We delete the
     * existing certificate and re-add the certificate in case there are differences that need to be applied.
     *
     * query should be made up of primary keys only. Optional/non-unique attributes in the query
     * can result in not finding the matching certificate and cause the update operation to fail.
     *
     * Certificate item primary keys used for the query:
     * kSecAttrSerialNumber: (CFStringRef) value indicates the item's serial number
     *      - We explicity set this value, extracted from the certificate itself as our primary method of determining
     * uniqueness of the certificate.
     *
     * Certificate primary keys we do not use for the query:
     * These can be added in the future if we require a more specified search query.
     * kSecAttrCertificateType: (CFNumberRef) value indicates the item's certificate type
     *      - values see the CSSM_CERT_TYPE enumeration in cssmtype.h
     * https://opensource.apple.com/source/Security/Security-55471/libsecurity_cssm/lib/cssmtype.h.auto.html
     *      - default will try to add common value such as X.509. We do not pass this attribute and allow default value
     * to be used. If we decide to support other types of certificates, we should set and use this value explicitly.
     * kSecAttrIssuer: (CFStringRef) value indicates the item's issuer
     *      - default will try to extract issuer from the certificate itself.
     *        We will not set this attribute and allow default value to be used.
     */
    if (status == errSecDuplicateItem) {
        AWS_LOGF_INFO(
            AWS_LS_IO_PKI,
            "static: Keychain contains existing certificate that was previously imported into the Keychain.  "
            "Deleting existing certificate in keychain.");

        delete_query =
            CFDictionaryCreateMutable(cf_alloc, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
        CFDictionaryAddValue(delete_query, kSecClass, kSecClassCertificate);
        CFDictionaryAddValue(delete_query, kSecAttrSerialNumber, serial_data);

        // delete the existing certificate from keychain
        status = SecItemDelete(delete_query);
        if (status != errSecSuccess) {
            AWS_LOGF_ERROR(AWS_LS_IO_PKI, "SecItemDelete certificate failed with OSStatus %d", (int)status);
            aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE);
            goto done;
        }

        // now try adding it again
        status = SecItemAdd(add_attributes, NULL);
        if (status != errSecSuccess) {
            AWS_LOGF_ERROR(AWS_LS_IO_PKI, "SecItemAdd certificate failed with OSStatus %d", (int)status);
            aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE);
            goto done;
        }
    }

    AWS_LOGF_INFO(AWS_LS_IO_PKI, "static: Successfully imported certificate into SecItem keychain.");

    result = AWS_OP_SUCCESS;

done:
    // cleanup
    aws_cf_release(add_attributes);
    aws_cf_release(delete_query);
    return result;
}

static int s_aws_secitem_add_private_key_to_keychain(
    CFAllocatorRef cf_alloc,
    SecKeyRef key_ref,
    CFStringRef label,
    CFDataRef application_label) {

    int result = AWS_OP_ERR;
    OSStatus status;

    CFMutableDictionaryRef add_attributes = NULL;
    CFMutableDictionaryRef delete_query = NULL;

    add_attributes =
        CFDictionaryCreateMutable(cf_alloc, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
    CFDictionaryAddValue(add_attributes, kSecClass, kSecClassKey);
    CFDictionaryAddValue(add_attributes, kSecAttrKeyClass, kSecAttrKeyClassPrivate);
    CFDictionaryAddValue(add_attributes, kSecAttrApplicationLabel, application_label);
    CFDictionaryAddValue(add_attributes, kSecAttrLabel, label);
    CFDictionaryAddValue(add_attributes, kSecValueRef, key_ref);

    // Initial attempt to add private key to keychain.
    status = SecItemAdd(add_attributes, NULL);

    // A duplicate item is handled. All other errors are unhandled.
    if (status != errSecSuccess && status != errSecDuplicateItem) {
        switch (status) {
            case errSecMissingEntitlement:
                AWS_LOGF_ERROR(
                    AWS_LS_IO_PKI,
                    "SecItemAdd private key failed with OSStatus %d : errSecMissingEntitlement. The process attempting "
                    "to access the keychain is missing the necessary entitlements.",
                    (int)status);
                break;
            default:
                AWS_LOGF_ERROR(AWS_LS_IO_PKI, "SecItemAdd private key failed with OSStatus %d", (int)status);
                aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE);
                break;
        }
        goto done;
    }

    /* A duplicate item error indicates the private key already exists in the keychain. We delete the
     * existing private key and re-add the private-key in case there are differences that need to be applied.
     *
     * query should be made up of primary keys only. Optional/non-unique attributes in the query
     * can result in not finding the matching private key and cause the update operation to fail.
     *
     * Private Key item primary keys we use for the query:
     * kSecAttrKeyClass: (CFTypeRef) value indicates item's cryptographic key class
     *      - We explicitly set this value to kSecAttrKeyClassPrivate
     * kSecAttrApplicationLabel: (CFStringRef) value indicates item's application label.
     *      - We pull this value out of the SecKeyRef. It's the hash of the public key stored within.
     *
     * Private Key primary keys we do not use for the query:
     * These can be added in the future if we require a more specified search query.
     * kSecAttrApplicationTag: (CFDataRef) value indicates the item's private tag.
     * kSecAttrKeySizeInBits: (CFNumberRef) value indicates the number of bits in a cryptographic key.
     * kSecAttrEffectiveKeySize: (CFNumberRef) value indicates the effective number of bits in a crytographic key.
     */

    if (status == errSecDuplicateItem) {
        AWS_LOGF_INFO(
            AWS_LS_IO_PKI,
            "static: Keychain contains existing private key that was previously imported into the Keychain.  "
            "Deleting private key in keychain.");

        delete_query =
            CFDictionaryCreateMutable(cf_alloc, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
        CFDictionaryAddValue(delete_query, kSecClass, kSecClassKey);
        CFDictionaryAddValue(delete_query, kSecAttrKeyClass, kSecAttrKeyClassPrivate);
        CFDictionaryAddValue(delete_query, kSecAttrApplicationLabel, application_label);

        // delete the existing private key from keychain
        status = SecItemDelete(delete_query);
        if (status != errSecSuccess) {
            AWS_LOGF_ERROR(AWS_LS_IO_PKI, "SecItemDelete private key failed with OSStatus %d", (int)status);
            aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE);
            goto done;
        }

        // now try adding it again
        status = SecItemAdd(add_attributes, NULL);
        if (status != errSecSuccess) {
            AWS_LOGF_ERROR(AWS_LS_IO_PKI, "SecItemAdd private key failed with OSStatus %d", (int)status);
            aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE);
            goto done;
        }
    }

    AWS_LOGF_INFO(AWS_LS_IO_PKI, "static: Successfully imported private key into SecItem keychain.");

    result = AWS_OP_SUCCESS;

done:
    // cleanup
    aws_cf_release(add_attributes);
    aws_cf_release(delete_query);

    return result;
}

static int s_aws_secitem_get_identity(CFAllocatorRef cf_alloc, CFDataRef serial_data, sec_identity_t *out_identity) {

    int result = AWS_OP_ERR;
    OSStatus status;
    CFMutableDictionaryRef search_query = NULL;
    SecIdentityRef sec_identity_ref = NULL;

    /*
     * SecItem identity is created when a certificate matches a private key in the keychain.
     * Since a private key may be associated with multiple certificates, searching for the
     * identity using a unique attribute of the certificate is required. This is why we use
     * the serial_data from the certificate as the search parameter.
     */
    search_query =
        CFDictionaryCreateMutable(cf_alloc, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
    CFDictionaryAddValue(search_query, kSecClass, kSecClassIdentity);
    CFDictionaryAddValue(search_query, kSecAttrSerialNumber, serial_data);
    CFDictionaryAddValue(search_query, kSecReturnRef, kCFBooleanTrue);

    /*
     * Copied or created CF items must have CFRelease called on them or you leak memory. This identity needs to
     * have CFRelease called on it at some point or it will leak.
     */
    status = SecItemCopyMatching(search_query, (CFTypeRef *)&sec_identity_ref);

    if (status != errSecSuccess) {
        AWS_LOGF_ERROR(AWS_LS_IO_PKI, "SecItemCopyMatching identity failed with OSStatus %d", (int)status);
        aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE);
        goto done;
    }

    *out_identity = sec_identity_create(sec_identity_ref);
    if (*out_identity == NULL) {
        AWS_LOGF_ERROR(
            AWS_LS_IO_PKI, "sec_identity_create failed to create a sec_identity_t from provided SecIdentityRef.");
        aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE);
        goto done;
    }

    AWS_LOGF_INFO(AWS_LS_IO_PKI, "static: Successfully retrieved identity from keychain.");

    result = AWS_OP_SUCCESS;

done:
    // cleanup
    aws_cf_release(search_query);
    aws_cf_release(sec_identity_ref);

    return result;
}

int aws_secitem_import_cert_and_key(
    struct aws_allocator *alloc,
    CFAllocatorRef cf_alloc,
    const struct aws_byte_cursor *public_cert_chain,
    const struct aws_byte_cursor *private_key,
    sec_identity_t *secitem_identity,
    const struct aws_secitem_options *secitem_options) {

    AWS_PRECONDITION(public_cert_chain != NULL);
    AWS_PRECONDITION(private_key != NULL);

    int result = AWS_OP_ERR;

    CFErrorRef error = NULL;

    CFDataRef cert_data = NULL;
    SecCertificateRef cert_ref = NULL;
    CFDataRef cert_serial_data = NULL;
    CFStringRef cert_label_ref = NULL;

    CFMutableDictionaryRef key_attributes = NULL;
    CFDictionaryRef key_copied_attributes = NULL;
    CFDataRef key_data = NULL;
    SecKeyRef key_ref = NULL;
    CFStringRef key_type = NULL;
    CFStringRef key_label_ref = NULL;
    CFDataRef application_label_ref = NULL;

    struct aws_array_list decoded_cert_buffer_list;
    AWS_ZERO_STRUCT(decoded_cert_buffer_list);
    struct aws_array_list decoded_key_buffer_list;
    AWS_ZERO_STRUCT(decoded_key_buffer_list);

    /*
     * iOS SecItem requires DER encoded files so we first convert the provided PEM encoded
     * cert and key into a list of aws_pem_object that strips headers/footers and Base64 decodes
     * the data into a byte buf.
     */
    if (aws_pem_objects_init_from_file_contents(&decoded_cert_buffer_list, alloc, *public_cert_chain)) {
        AWS_LOGF_ERROR(AWS_LS_IO_PKI, "static: Failed to decode PEM certificate to DER format.");
        goto done;
    }
    AWS_ASSERT(aws_array_list_is_valid(&decoded_cert_buffer_list));

    if (aws_pem_objects_init_from_file_contents(&decoded_key_buffer_list, alloc, *private_key)) {
        AWS_LOGF_ERROR(AWS_LS_IO_PKI, "static: Failed to decode PEM private key to DER format.");
        goto done;
    }
    AWS_ASSERT(aws_array_list_is_valid(&decoded_key_buffer_list));

    /*
     * A PEM certificate file could contains multiple PEM data sections. We currently decode and
     * use the first certificate data only. Certificate chaining support could be added in the future.
     */
    if (aws_array_list_length(&decoded_cert_buffer_list) > 1) {
        AWS_LOGF_ERROR(AWS_LS_IO_PKI, "Certificate chains not currently supported on iOS.");
        result = aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
        goto done;
    }

    /* Convert the DER encoded files to the CFDataRef type required for import into keychain */
    struct aws_pem_object *pem_cert_ptr = NULL;
    aws_array_list_get_at_ptr(&decoded_cert_buffer_list, (void **)&pem_cert_ptr, 0);
    AWS_ASSERT(pem_cert_ptr);

    struct aws_pem_object *pem_key_ptr = NULL;
    aws_array_list_get_at_ptr(&decoded_key_buffer_list, (void **)&pem_key_ptr, 0);
    AWS_ASSERT(pem_key_ptr);

    cert_data = CFDataCreate(cf_alloc, pem_cert_ptr->data.buffer, pem_cert_ptr->data.len);
    if (!cert_data) {
        AWS_LOGF_ERROR(AWS_LS_IO_PKI, "Error creating certificate data system call.");
        aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE);
        goto done;
    }

    key_data = CFDataCreate(cf_alloc, pem_key_ptr->data.buffer, pem_key_ptr->data.len);
    if (!key_data) {
        AWS_LOGF_ERROR(AWS_LS_IO_PKI, "Error creating private key data system call.");
        aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE);
        goto done;
    }

    /*
     * The aws_pem_object preserves the type of encoding found in the PEM file. We use the type_string member to set the
     * appropriate CFStringRef key_type attribute.
     */
    switch (pem_key_ptr->type) {
        case AWS_PEM_TYPE_PRIVATE_RSA_PKCS1:
            key_type = kSecAttrKeyTypeRSA;
            break;

        case AWS_PEM_TYPE_EC_PRIVATE:
            key_type = kSecAttrKeyTypeEC;
            break;

        case AWS_PEM_TYPE_PRIVATE_PKCS8:
            /*
             * PKCS8 is not supported on iOS/tvOS (the framework doesn't allow it) and is currently NOT supported by us
             * on macOS PKCS8 support for macOS using SecItem can be added later for macOS only but will require a
             * different import strategy than the currently shared one.
             */
            key_type = kSecAttrKeyTypeRSA;
            AWS_LOGF_ERROR(
                AWS_LS_IO_PKI, "The PKCS8 private key format is currently unsupported for use with SecItem.");
            aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
            goto done;
            break;

        case AWS_PEM_TYPE_UNKNOWN:
        default:
            AWS_LOGF_ERROR(AWS_LS_IO_PKI, "Unsupported private key format.");
            aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
            goto done;
    }

    /* Attributes used for query and adding of cert/key SecItems */

    /*
     * We create a SecCertificateRef here to use with the kSecValueRef key as well as to extract the serial number for
     * use as a unique identifier when storing the certificate in the keychain. The serial number is also used as the
     * identifier when retrieving the identity
     */
    cert_ref = SecCertificateCreateWithData(cf_alloc, cert_data);
    if (!cert_ref) {
        AWS_LOGF_ERROR(AWS_LS_IO_PKI, "Failed creating SecCertificateRef from cert_data.");
        aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE);
        goto done;
    }

    cert_serial_data = SecCertificateCopySerialNumberData(cert_ref, &error);
    if (error) {
        AWS_LOGF_ERROR(AWS_LS_IO_PKI, "Failed extracting serial number data from cert_ref.");
        aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE);
        goto done;
    }

    cert_label_ref = CFStringCreateWithBytes(
        cf_alloc,
        (const UInt8 *)aws_string_bytes(secitem_options->cert_label),
        secitem_options->cert_label->len,
        kCFStringEncodingUTF8,
        false);
    if (!cert_label_ref) {
        AWS_LOGF_ERROR(AWS_LS_IO_PKI, "Failed creating certificate label.");
        aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE);
        goto done;
    }

    /*
     * We create a SecKeyRef (key_ref) here using the key_data for the purpose of extracting the public key hash from
     * the private key. We need the public key hash (application_label_ref) to use as a unique identifier when importing
     * the private key into the keychain.
     */
    key_attributes =
        CFDictionaryCreateMutable(cf_alloc, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
    CFDictionaryAddValue(key_attributes, kSecAttrKeyClass, kSecAttrKeyClassPrivate);
    CFDictionaryAddValue(key_attributes, kSecAttrKeyType, key_type);
    key_ref = SecKeyCreateWithData(key_data, key_attributes, &error);

    // Get the hash of the public key stored within the private key by extracting it from the key_ref's attributes
    key_copied_attributes = SecKeyCopyAttributes(key_ref);
    // application_label_ref does not need to be released. It gets released when key_copied_attributes is released.
    application_label_ref = (CFDataRef)CFDictionaryGetValue(key_copied_attributes, kSecAttrApplicationLabel);
    if (!application_label_ref) {
        AWS_LOGF_ERROR(AWS_LS_IO_PKI, "Failed creating private key application label.");
        aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE);
        goto done;
    }

    key_label_ref = CFStringCreateWithBytes(
        cf_alloc,
        (const UInt8 *)aws_string_bytes(secitem_options->key_label),
        secitem_options->key_label->len,
        kCFStringEncodingUTF8,
        false);
    if (!key_label_ref) {
        AWS_LOGF_ERROR(AWS_LS_IO_PKI, "Failed creating private key label.");
        aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE);
        goto done;
    }

    // Add the certificate and private key to keychain then retrieve identity

    if (s_aws_secitem_add_certificate_to_keychain(cf_alloc, cert_ref, cert_serial_data, cert_label_ref)) {
        goto done;
    }

    if (s_aws_secitem_add_private_key_to_keychain(cf_alloc, key_ref, key_label_ref, application_label_ref)) {
        goto done;
    }

    if (s_aws_secitem_get_identity(cf_alloc, cert_serial_data, secitem_identity)) {
        goto done;
    }

    result = AWS_OP_SUCCESS;

done:
    // cleanup
    aws_cf_release(error);
    aws_cf_release(cert_data);
    aws_cf_release(cert_ref);
    aws_cf_release(cert_serial_data);
    aws_cf_release(cert_label_ref);
    aws_cf_release(key_attributes);
    aws_cf_release(key_copied_attributes);
    aws_cf_release(key_data);
    aws_cf_release(key_ref);
    aws_cf_release(key_type);
    aws_cf_release(key_label_ref);

    // Zero out the array list and release it
    aws_pem_objects_clean_up(&decoded_cert_buffer_list);
    aws_pem_objects_clean_up(&decoded_key_buffer_list);

    return result;
}

int aws_secitem_import_pkcs12(
    CFAllocatorRef cf_alloc,
    const struct aws_byte_cursor *pkcs12_cursor,
    const struct aws_byte_cursor *password,
    sec_identity_t *out_identity) {

    int result = AWS_OP_ERR;
    CFArrayRef items = NULL;
    CFDataRef pkcs12_data = NULL;
    CFMutableDictionaryRef dictionary = NULL;
    SecIdentityRef sec_identity_ref = NULL;
    CFStringRef password_ref = NULL;

    pkcs12_data = CFDataCreate(cf_alloc, pkcs12_cursor->ptr, pkcs12_cursor->len);
    if (!pkcs12_data) {
        AWS_LOGF_ERROR(AWS_LS_IO_PKI, "Error creating pkcs12 data system call.");
        aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE);
        goto done;
    }

    if (password->len) {
        password_ref = CFStringCreateWithBytes(cf_alloc, password->ptr, password->len, kCFStringEncodingUTF8, false);
    } else {
        password_ref = CFStringCreateWithCString(cf_alloc, "", kCFStringEncodingUTF8);
    }

    dictionary = CFDictionaryCreateMutable(cf_alloc, 0, NULL, NULL);
    CFDictionaryAddValue(dictionary, kSecImportExportPassphrase, password_ref);

    OSStatus status = SecPKCS12Import(pkcs12_data, dictionary, &items);

    if (status != errSecSuccess || CFArrayGetCount(items) == 0) {
        AWS_LOGF_ERROR(AWS_LS_IO_PKI, "Failed to import PKCS#12 file with OSStatus:%d", (int)status);
        aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE);
        goto done;
    }

    // Extract the identity from the first item in the array
    // identity_and_trust does not need to be released as it is not a copy or created CF object.
    CFDictionaryRef identity_and_trust = CFArrayGetValueAtIndex(items, 0);
    sec_identity_ref = (SecIdentityRef)CFDictionaryGetValue(identity_and_trust, kSecImportItemIdentity);

    if (sec_identity_ref != NULL) {
        AWS_LOGF_INFO(
            AWS_LS_IO_PKI, "static: Successfully imported PKCS#12 file into keychain and retrieved identity.");
    } else {
        status = errSecItemNotFound;
        AWS_LOGF_ERROR(AWS_LS_IO_PKI, "Failed to retrieve identity from PKCS#12 with OSStatus %d", (int)status);
        goto done;
    }

    *out_identity = sec_identity_create(sec_identity_ref);

    result = AWS_OP_SUCCESS;

done:
    // cleanup
    aws_cf_release(pkcs12_data);
    aws_cf_release(dictionary);
    aws_cf_release(password_ref);
    aws_cf_release(items);
    return result;
}

int aws_import_trusted_certificates(
    struct aws_allocator *alloc,
    CFAllocatorRef cf_alloc,
    const struct aws_byte_cursor *certificates_blob,
    CFArrayRef *certs) {
    AWS_PRECONDITION(certificates_blob != NULL);

    struct aws_array_list certificates;

    if (aws_pem_objects_init_from_file_contents(&certificates, alloc, *certificates_blob)) {
        AWS_LOGF_ERROR(AWS_LS_IO_PKI, "static: decoding CA PEM failed.");
        aws_array_list_clean_up(&certificates);
        return AWS_OP_ERR;
    }

    size_t cert_count = aws_array_list_length(&certificates);
    CFMutableArrayRef temp_cert_array = CFArrayCreateMutable(cf_alloc, cert_count, &kCFTypeArrayCallBacks);

    int err = AWS_OP_SUCCESS;
    aws_mutex_lock(&s_sec_mutex);
    for (size_t i = 0; i < cert_count; ++i) {
        struct aws_pem_object *pem_object_ptr = NULL;
        aws_array_list_get_at_ptr(&certificates, (void **)&pem_object_ptr, i);

        CFDataRef cert_blob = CFDataCreate(cf_alloc, pem_object_ptr->data.buffer, pem_object_ptr->data.len);

        if (cert_blob) {
            SecCertificateRef certificate_ref = SecCertificateCreateWithData(cf_alloc, cert_blob);
            CFArrayAppendValue(temp_cert_array, certificate_ref);
            CFRelease(certificate_ref);
            CFRelease(cert_blob);
        } else {
            err = AWS_OP_SUCCESS;
        }
    }
    aws_mutex_unlock(&s_sec_mutex);

    *certs = temp_cert_array;
    aws_pem_objects_clean_up(&certificates);
    aws_array_list_clean_up(&certificates);
    return err;
}