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.