TLS Connection with mutual authentication, using Enclave-based private/public keys

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.

Replies

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"

Thanks for answering!


We have generated the pk12 as follows:


openssl ecparam -name secp256r1 -genkey -noout -out secp256-key.pem
openssl req -out secp256.csr -key secp256-key.pem -new
openssl req -in secp256.csr -text -noout
openssl x509 -signkey secp256-key.pem -req -in secp256.csr -out secp256.cer
openssl pkcs12 -export -out secp256.p12 -in secp256.cer -inkey secp256-key.pem


With the generated pkcs12 file, we have tried two cases:


- Creating identity directly from pkcs12 file

- Importing it to the keychain, and then trying the identity from the keychain.


Looks like both cases work just fine. The signature during CertificateVerify message is SHA256withECDSA, as expected.

We also compared the certificate generated by openssl with certificate generated by the secure enclave, they look practically identical, except of course for the public key and signature.


So it seems that the problem manifests itself only with private key that is kept in the secure enclave.


Thank you.

So it seems that the problem manifests itself only with private key that is kept in the secure enclave.

OK, that’s an important factoid. Thanks for checking.

Honestly, I don’t know the current state of how TLS interacts with keys stored in the secure enclave. I pinged the engineering team about this and they have a similar opinion to mine, that is, in theory this should work (of course we all know that the difference between theory and practice is much greater in practice than it is in theory:-).

Please file a bug with the details of the problem you’re seeing then post the bug number here.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks Quinn,


The bug number is 25978027.

I ended up investigating this issue in depth. The underlying problem here is that Secure Transport relies on a particular piece of metadata about the key (accessed via a private API) and iOS has a bug (r. 25,978,027) that causes a key created in the Secure Enclave to return the wrong value. Until this bug is fixed, there’s no way to use a Secure Enclave key as part of a Secure Transport client identity.

Bummer.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

(s. 640,199,604)

Thanks a lot for investigation, Quinn. We will just wait for the fix then, applying some workarounds for the time being.

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

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];
}

Thank you for your help. I will try and let you know.


Thanks Again.

Since 25978027 isn't on OpenRadar, do you have any information of what happened to this bug?

There’s a good chance that this bug in fixed in iOS 10. It’s hard to be 100% sure because kusandr’s bug report (r. 25978027) has lost its way a little. Regardless, it’s definitely worth trying this out on the GM seed.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Do you know the status of this bug? Is it now possible (with iOS 11) to perform a TLS client authentication with a private key that is stored within the Secure Enclave?


Regards, Alex

Is this fixed, do we have some example of this working?
I suspect it is fixed but I haven’t had a chance to test it. If no one else chimes in, my advice is for you to suck it and see.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
Hey hey, my link to the su‍ck it and see censorship was censored by DevForums. And they say that American’s don’t understand irony (-:

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"