11 Replies
      Latest reply: Sep 11, 2016 7:39 AM by eskimo RSS
      kusandr Level 1 Level 1 (0 points)

        Hello to all,

         

        Recently, we have been trying to establish a mutually authenticated TLS connection between iOS9 device and Java server, using kSecAttrTokenIDSecureEnclave key pair on the iOS device, so that private key would never leave the Enclave. Long story short, we managed to:

         

        a. Generate private/public key pair with SecKeyTypeEC and private key in Secure Enclave

        b. Create a CSR on device and sign it with the SecKeyRawSign method

        c. Sign the CSR with our own CA on server

        d. Add the signed certificate to the device’s keychain, creating relevant identity.

        e. Provide this identity to iOS TLS implementation using NSURLAuthenticationMethodClientCertificate

         

        Now, as kSecAttrTokenIDSecureEnclave generates a key-pair using Elliptic Curve algorithm, and certificate is created with EC public key, and the identity is successfully established when importing the certificate to keychain, it is expected that client certificate verification message (CertificateVerify) would use EC-related signature, e.g. SHA1withECDSA.

         

        However, the signature that iOS TLS generates during the CertificateVerify is of a different type: SHA256withRSA. Also, the signature size is rather small: 256 bits, as if short 256-bit modulus is being used for the signature. Can anyone advise if this behavior is expected, or do we do anything incorrectly? Please find the implementation details below.

         

        Thank you.

         

        ========================================================================

        Some implementation details

        ========================================================================

         

        1. Generate key pair


        - (BOOL)generateKeyPair:(NSError *__autoreleasing *)error {
            BOOL retVal = NO;
            NSError *ne = nil;
            CFErrorRef err = (__bridge CFErrorRef)ne;
            // NOTE: ACL creation is very fragile and flag sensitive!
            SecAccessControlRef sac = SecAccessControlCreateWithFlags(kCFAllocatorSystemDefault,
                                                                      kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
                                                                      kSecAccessControlUserPresence|kSecAccessControlPrivateKeyUsage,
                                                                      &err);
          
            NSDictionary *privateKeyDict = @{
                                             (__bridge id)kSecAttrIsPermanent : @YES,
                                             (__bridge id)kSecAttrAccessControl : (__bridge_transfer id)sac
                                             };
        
            NSDictionary *pubKeyDict = @{
                                         (__bridge id)kSecAttrIsPermanent : @NO,
            };
          
            NSDictionary *keyPairDict = @{
                                          (__bridge id)kSecAttrKeyType : (__bridge id)kSecAttrKeyTypeEC,
                                          (__bridge id)kSecAttrTokenID : (__bridge id)kSecAttrTokenIDSecureEnclave,
                                          (__bridge id)kSecAttrKeySizeInBits : @256,
                                          (__bridge id)kSecAttrApplicationTag : kAppTag,
                                          (__bridge id)kSecPrivateKeyAttrs : privateKeyDict,
                                          (__bridge id)kSecPublicKeyAttrs : pubKeyDict
                                          };
            SecKeyRef pubKey = NULL;
            SecKeyRef privateKey = NULL;
            OSStatus success = SecKeyGeneratePair(
                                                  (__bridge CFDictionaryRef)keyPairDict,
                                                  &pubKey,
                                                  &privateKey
                                                  );
            if (errSecSuccess != success) {
          // log error
            }
            else {
                // Add public key to normal keychain
                NSDictionary *params = @{
                                         (__bridge id)kSecClass : (__bridge id)kSecClassKey,
                                         (__bridge id)kSecAttrKeyClass : (__bridge id)kSecAttrKeyClassPublic,
                                         (__bridge id)kSecValueRef : (__bridge id)pubKey,
                                         (__bridge id)kSecAttrKeyType : (__bridge id)kSecAttrKeyTypeEC,
                                         (__bridge id)kSecAttrApplicationTag : kAppTag,
                                         (__bridge id)kSecReturnData : @YES
                                         };
                success = SecItemAdd((__bridge CFDictionaryRef)params, NULL);
                if (errSecSuccess != success) {
                    // log error
                }
                else {
                    retVal = YES;
                }
            }
            return retVal;
        }
        


        2. Generate CSR

         

        For this step, we are using modified SCCSR class [https://github.com/ateska/ios-csr/blob/master/SCCSR.m].

         

        static uint8_t ec_enc_head[26] = {0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01, 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00};
        - (NSData *)buildPublicKeyInfo:(NSData *)publicKeyBits {
            NSMutableData *publicKeyInfo = [NSMutableData dataWithBytes:ec_enc_head length:sizeof(ec_enc_head)];
          [publicKeyInfo appendData:publicKeyBits];
        
          return publicKeyInfo;
        }
        // - calculate digest of request
          CC_SHA1_CTX SHA1;
          CC_SHA1_Init(&SHA1);
          CC_SHA1_Update(&SHA1, [certificationRequestInfo mutableBytes], (unsigned int)certificationRequestInfo.length);
          unsigned char digest[CC_SHA1_DIGEST_LENGTH];
          CC_SHA1_Final(digest, &SHA1);
        
        // - sign with private key
          uint8_t signature[128];
          size_t signature_len = sizeof(signature);
          OSStatus osrc = SecKeyRawSign(
          privateKey,
          kSecPaddingPKCS1,
          digest, sizeof(digest),
          signature, &signature_len
          );
        
        static uint8_t seq_ec_sha1[] = {0x30, 0x09, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x01};
        // - finish building CSR as NSData by appending request info, seq_ec_sha1, signature, enclosing in sequence
        

        3. Signing the CSR

         

        Here we would use openssl commands, signing the generated CSR. The typical openssl command sequence would be:

         

          a. Convert generated CSR from DER format to PEM format

         

          openssl req -in generated.csr -inform DER -out generated.csr.pem -outform PEM

         

          b. Sign the PEM certificate

         

          openssl x509 -req -CA local-ca-certificate.pem -in generated.csr.pem -CAkey local-ca-key.pem -out signed.cer.pem -days 365 -CAcreateserial

         

          c. Convert the key into DER format for iOS to digest

         

          openssl x509 -in signed.cer.pem -inform PEM -out signed.cer.der -outform DER

         

        4. Import signed certificate (for the sake of Proof of Concept, we did it simply bundling signed.cer.der into the application)

         

        - (BOOL)importCertificateFromData:(NSData *)data error:(NSError **)error {
            BOOL retVal = NO;
            CFDataRef dataRef = (__bridge CFDataRef)data;
            OSStatus success = errSecSuccess;
          
            SecCertificateRef cert = SecCertificateCreateWithData(NULL, dataRef);
            if (NULL != cert) {
          // try evaluate (noone  knows why?)
                SecTrustRef trust = NULL;
                if (noErr == SecTrustCreateWithCertificates(cert, nil, &trust)) {
                    SecTrustResultType result;
                    success = SecTrustEvaluate(trust, &result);
                    if (result == kSecTrustResultProceed || result == kSecTrustResultUnspecified) {              
                    }
                    else if (kSecTrustResultRecoverableTrustFailure == result) {
                    }
                    NSLog(@"secTrustEval = %u | %@", result, [self p_stringFromStatus:success]);
                }
              
                NSDictionary *params = @{
                                         (__bridge id)kSecClass : (__bridge id)kSecClassCertificate,
                                         (__bridge id)kSecValueRef : (__bridge id)cert,
                                         (__bridge id)kSecAttrLabel : kLabel,
                                         (__bridge id)kSecValueData : (__bridge id)dataRef,
                                         (__bridge id)kSecReturnAttributes : @YES,
                                         (__bridge id)kSecReturnRef : @YES,
                                         (__bridge id)kSecReturnData : @YES
                                       
                                         };
                CFTypeRef retData = NULL;
                success = SecItemAdd((__bridge CFDictionaryRef)params, &retData);
        
                if (errSecSuccess == success) {
                    retVal = YES;
                }
                else {
                   // log error
                }
                CFRelease(cert);
            }
            return retVal;
        }
        

        5. Construct credential for NSURLAuthenticationMethodClientCertificate challenge

         

            NSURLCredential *credential = nil;
            NSDictionary *params = @{
                                     (__bridge id)kSecClass : (__bridge id)kSecClassIdentity,
                                     (__bridge id)kSecReturnRef : @YES
                                     };
          
            CFTypeRef data = NULL;
            OSStatus success = SecItemCopyMatching((__bridge CFDictionaryRef)params, &data);
            if (errSecSuccess == success) {
                SecIdentityRef ident = (SecIdentityRef)data;
        
        
                SecCertificateRef icert = NULL;
                SecIdentityCopyCertificate(ident, &icert);
        
        
                credential = [NSURLCredential credentialWithIdentity:ident certificates:[NSArray arrayWithObject:(__bridge_transfer id)icert]
                                                     persistence:NSURLCredentialPersistenceNone];
            }
            return credential;
        

        6. Observe handshake failure, as server does not accept SHA256withRSA signature during CertificateVerify message, as public key in the Certificate is Elliptic Curve.

        • Re: TLS Connection with mutual authentication, using Enclave-based private/public keys
          eskimo Apple Staff Apple Staff (7,550 points)

          My understanding is that, unlike earlier versions of iOS, iOS 9 should be able to use an EC digital identity as a client identity.  I haven’t personally tested that.  I recommend you test that before you go further (that is, cut the secure enclave out of the equation temporarily).  That would involve:

          1. not on iOS, generate a test EC digital identity that closely matches the one you generate via your secure enclave process

          2. put it in an a PKCS#12

          3. import that into the keychain on iOS

          4. try to form a TLS connection that’s authenticated via that digital identity

          That will tell you whether the problem is with EC digital identities or with secure enclave digital identities, and we can pick things up from there.

          Share and Enjoy

          Quinn “The Eskimo!”
          Apple Developer Relations, Developer Technical Support, Core OS/Hardware
          let myEmail = "eskimo" + "1" + "@apple.com"

          • Re: TLS Connection with mutual authentication, using Enclave-based private/public keys
            WIDEPOINT Level 1 Level 1 (0 points)

            Hi Kusndr,

             

            This is regarding CSR generation(.csr file) after generating Keys(private & public) on the secure enclave. After generating the .csr file, getting parsing error when try to use "openssl req -in generated.csr -inform DER -out generated.csr.pem -outform PEM". Am i missing anythig here, please? any help is appreciated!

             

            This is what I am using:

             

            static uint8_t OBJECT_commonName[5] = {0x06, 0x03, 0x55, 0x04, 0x03};

            static uint8_t OBJECT_countryName[5] = {0x06, 0x03, 0x55, 0x04, 0x06};

            static uint8_t OBJECT_organizationName[5] = {0x06, 0x03, 0x55, 0x04, 0x0A};

            static uint8_t OBJECT_organizationalUnitName[5] = {0x06, 0x03, 0x55, 0x04, 0x0B};

            static uint8_t OBJECT_rsaEncryptionNULL[13] = {0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00};

            static uint8_t SEQUENCE_OBJECT_sha1WithRSAEncryption[] = {0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 1, 1, 5, 0x05, 0x00};

            static uint8_t seq_ec_sha1[] = {0x30, 0x09, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x01};

            static uint8_t SEQUENCE_tag = 0x30;

            static uint8_t SET_tag = 0x31;

            static uint8_t ec_enc_head[26] = {0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01, 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00};

            @implementation SCCSR

            @synthesize countryName;

            @synthesize organizationName;

            @synthesize organizationalUnitName;

            @synthesize commonName;

            @synthesize subjectDER;

            -(SCCSR *)init

            {

                self = [super init];

                if (!self) return self;

                countryName = nil;

                organizationName = nil;

                organizationalUnitName = nil;

                commonName = nil;

                subjectDER = nil;

             

                return self;

            }

            -(NSData *) build:(NSData *)publicKeyBits privateKey:(SecKeyRef)privateKey

            {

                NSMutableData * CertificationRequestInfo = [self buildCertificationRequestInfo:publicKeyBits];

             

             

                /

                CC_SHA1_CTX SHA1;

                CC_SHA1_Init(&SHA1);

                CC_SHA1_Update(&SHA1, [CertificationRequestInfo mutableBytes], (unsigned int)CertificationRequestInfo.length);

                unsigned char digest[CC_SHA1_DIGEST_LENGTH];

                CC_SHA1_Final(digest, &SHA1);

             

                /

                uint8_t signature[128];

                size_t signature_len = sizeof(signature);

                OSStatus osrc = SecKeyRawSign(

                                              privateKey,

                                              kSecPaddingPKCS1,

                                              digest, sizeof(digest),

                                              signature, &signature_len

                                              );

             

                assert(osrc == noErr);

             

                NSMutableData * CertificationRequest = [[NSMutableData alloc] initWithCapacity:1024];

                [CertificationRequest appendData:CertificationRequestInfo];

                [CertificationRequest appendBytes:seq_ec_sha1 length:sizeof(seq_ec_sha1)];

             

                NSMutableData * signdata = [NSMutableData dataWithCapacity:257];

                uint8_t zero = 0;

                [signdata appendBytes:&zero length:1]; /

                [signdata appendBytes:signature length:signature_len];

                [SCCSR appendBITSTRING:signdata into:CertificationRequest];

             

                [SCCSR enclose:CertificationRequest by:SEQUENCE_tag]; /

             

                return CertificationRequest;

            }

            -(NSMutableData *)buildCertificationRequestInfo:(NSData *)publicKeyBits

            {

                NSMutableData * CertificationRequestInfo = [[NSMutableData alloc] initWithCapacity:512];

             

                /

                uint8_t version[3] = {0x02, 0x01, 0x00}; /

                [CertificationRequestInfo appendBytes:version length:sizeof(version)];

             

             

                /

                NSMutableData * Subject = [[NSMutableData alloc] initWithCapacity:256];

                if (countryName != nil) [SCCSR appendSubjectItem:OBJECT_countryName value:countryName into:Subject];

                if (organizationName != nil) [SCCSR appendSubjectItem:OBJECT_organizationName value:organizationName into:Subject];

                if (organizationalUnitName != nil) [SCCSR appendSubjectItem:OBJECT_organizationalUnitName value:organizationalUnitName into:Subject];

                if (commonName != nil) [SCCSR appendSubjectItem:OBJECT_commonName value:commonName into:Subject];

                [SCCSR enclose:Subject by:SEQUENCE_tag]; /

             

                subjectDER = [NSData dataWithData:Subject];

             

                [CertificationRequestInfo appendData:Subject];

                /

                NSData * publicKeyInfo = [SCCSR buildPublicKeyInfo1:publicKeyBits];

                [CertificationRequestInfo appendData:publicKeyInfo];

             

                /

                uint8_t attributes[2] = {0xA0, 0x00};

                [CertificationRequestInfo appendBytes:attributes length:sizeof(attributes)];

             

             

                [SCCSR enclose:CertificationRequestInfo by:SEQUENCE_tag]; /

             

                return CertificationRequestInfo;

            }

            /

            +(NSData *)buildPublicKeyInfo:(NSData *)publicKeyBits

            {

                NSMutableData * publicKeyInfo = [[NSMutableData alloc] initWithCapacity:390];

             

                [publicKeyInfo appendBytes:OBJECT_rsaEncryptionNULL length:sizeof(OBJECT_rsaEncryptionNULL)];

                [SCCSR enclose:publicKeyInfo by:SEQUENCE_tag]; /

             

                NSMutableData * publicKeyASN = [[NSMutableData alloc] initWithCapacity:260];

             

                NSData * mod = [SCCSR getPublicKeyMod:publicKeyBits];

                char Integer = 0x02; /

                [publicKeyASN appendBytes:&Integer length:1];

                [SCCSR appendDERLength:[mod length] into:publicKeyASN];

                [publicKeyASN appendData:mod];

             

                NSData * exp = [SCCSR getPublicKeyExp:publicKeyBits];

                [publicKeyASN appendBytes:&Integer length:1];

                [SCCSR appendDERLength:[exp length] into:publicKeyASN];

                [publicKeyASN appendData:exp];

             

                [SCCSR enclose:publicKeyASN by:SEQUENCE_tag]; /

                [SCCSR prependByte:0x00 into:publicKeyASN]; /

             

                [SCCSR appendBITSTRING:publicKeyASN into:publicKeyInfo];

             

                [SCCSR enclose:publicKeyInfo by:SEQUENCE_tag]; /

             

                return publicKeyInfo;

            }

            +(void)appendSubjectItem:(const uint8_t[5])what value:(NSString *)value into:(NSMutableData *)into

            {

                NSMutableData * SubjectItem = [[NSMutableData alloc] initWithCapacity:128];

                [SubjectItem appendBytes:what length:5];

                [SCCSR appendUTF8String:value into:SubjectItem];

                [SCCSR enclose:SubjectItem by:SEQUENCE_tag]; /

                [SCCSR enclose:SubjectItem by:SET_tag]; /

             

                [into appendData:SubjectItem];

            }

            +(void)appendUTF8String:(NSString *)string into:(NSMutableData *)into

            {

                char strtype = 0x0C; /

                [into appendBytes:&strtype length:1];

                [SCCSR appendDERLength:[string lengthOfBytesUsingEncoding:NSUTF8StringEncoding] into:into];

                [into appendData:[string dataUsingEncoding:NSUTF8StringEncoding]];

            }

            +(void)appendDERLength:(size_t)length into:(NSMutableData *)into

            {

                assert(length < 0x8000);

             

                if (length < 128)

                {

                    uint8_t d = length;

                    [into appendBytes:&d length:1];

                }

                else if (length < 0x100)

                {

                    uint8_t d[2] = {0x81, length & 0xFF};

                    [into appendBytes:&d length:2];

                }

                else if (length < 0x8000)

                {

                    uint8_t d[3] = {0x82, (length & 0xFF00) >> 8, length & 0xFF};

                    [into appendBytes:&d length:3];

                }

            }

            +(void)appendBITSTRING:(NSData *)data into:(NSMutableData *)into

            {

                char strtype = 0x03; /

                [into appendBytes:&strtype length:1];

                [SCCSR appendDERLength:[data length] into:into];

                [into appendData:data];

            }

            +(void)enclose:(NSMutableData *)data by:(uint8_t)by

            {

                NSMutableData* newdata = [[NSMutableData alloc]initWithCapacity:[data length]+4];

             

                [newdata appendBytes:&by length:1];

                [SCCSR appendDERLength:[data length] into:newdata];

                [newdata appendData:data];

             

                [data setData:newdata];

            }

            +(void)prependByte:(uint8_t)byte into:(NSMutableData *)into

            {

                NSMutableData* newdata = [[NSMutableData alloc]initWithCapacity:[into length]+1];

             

                [newdata appendBytes:&byte length:1];

                [newdata appendData:into];

             

                [into setData:newdata];

            }

            + (NSData *)getPublicKeyExp:(NSData *)publicKeyBits

            {

                int iterator = 0;

             

                iterator++; /

                [SCCSR derEncodingGetSizeFrom:publicKeyBits at:&iterator]; /

             

                iterator++; /

                int mod_size = [SCCSR derEncodingGetSizeFrom:publicKeyBits at:&iterator];

                iterator += mod_size;

             

                iterator++; /

                int exp_size = [SCCSR derEncodingGetSizeFrom:publicKeyBits at:&iterator];

             

                return [publicKeyBits subdataWithRange:NSMakeRange(iterator, exp_size)];

            }

            +(NSData *)getPublicKeyMod:(NSData *)publicKeyBits

            {

                int iterator = 0;

             

                iterator++; /

                [SCCSR derEncodingGetSizeFrom:publicKeyBits at:&iterator]; /

             

                iterator++; /

                int mod_size = [SCCSR derEncodingGetSizeFrom:publicKeyBits at:&iterator];

             

                return [publicKeyBits subdataWithRange:NSMakeRange(iterator, mod_size)];

            }

            +(int)derEncodingGetSizeFrom:(NSData*)buf at:(int*)iterator

            {

                const uint8_t* data = [buf bytes];

                int itr = *iterator;

                int num_bytes = 1;

                int ret = 0;

             

                if (data[itr] > 0x80) {

                    num_bytes = data[itr] - 0x80;

                    itr++;

                }

             

                for (int i = 0 ; i < num_bytes; i++) ret = (ret * 0x100) + data[itr + i];

             

                *iterator = itr + num_bytes;

                return ret;

            }

            + (NSData *)buildPublicKeyInfo1:(NSData *)publicKeyBits

            {

                NSMutableData *publicKeyInfo = [NSMutableData dataWithBytes:ec_enc_head length:sizeof(ec_enc_head)];

                [publicKeyInfo appendData:publicKeyBits];

             

                return publicKeyInfo;

            }

            @end

              • Re: TLS Connection with mutual authentication, using Enclave-based private/public keys
                k_sh Level 1 Level 1 (0 points)

                Try to build final request without specifying capacities, like so:

                -(NSData *) build:(NSData *)publicKeyBits privateKey:(SecKeyRef)privateKey {
                     // ... your signature and request generation code
                     NSMutableData * CertificationRequest = [[NSMutableData alloc] init];
                     [CertificationRequest appendData:CertificationRequestInfo];
                     [CertificationRequest appendBytes:seq_ec_sha1 length:sizeof(seq_ec_sha1)];
                       
                     NSMutableData * signdata = [NSMutableData data];
                     uint8_t zero = 0;
                     [signdata appendBytes:&zero length:1];
                     [signdata appendBytes:signature length:signature_len];
                     [SCCSR appendBITSTRING:signdata into:CertificationRequest];
                       
                     [SCCSR enclose:CertificationRequest by:SEQUENCE_tag];
                       
                     return [CertificationRequest copy];
                }