MehtaiPhoneApps MehtaiPhoneApps - 5 months ago 107
iOS Question

Generate a CSR in iOS with KeyChain

I want to generate a CSR (Certificate Signing Request) in iOS, sign it with a private key, and then send the CSR request back to the server encoded in PEM format

The key will be in a extension or generated in iOS KeyChain

EDITED

The system is part of a PKI (Private Key Infraestructure) and needs to work with standard formats because has to be signed with server private key in a standard way.

The security framework and cryptographic functions available for iOS developers are obscure and not well documented, and thought to be used only inside iOS. There is no easy way to integrate iOS cryptographic in a general system: importing and exporting keys, working with known formats like PEM, CSR or pkcs#7

The library ios-csr do part of the work

//SCCSR from ios-csr library
let sccsr : SCCSR = SCCSR()
sccsr.commonName = CN;
let certificateRequest = sccsr.build(publicKey, privateKey: privateKey)


We need help to generate a key with iOS KeyChain to be used in SCCSR and converting the result to PEM format

Answer

Yes, it is possible but is not simple at all because iOS do not work with standard formats for keys as you could think

Generate CSR as PEM

I have used this library successfully to generate a CSR in PCKS#10 format with a key generated in KeyChain and encoded in DER format (binary).

https://github.com/ateska/ios-csr

func createCertificationRequest(keyId: String, C: String?, CN: String?, O:String?, SERIALNAME:String? ) -> String {

        //Replace this with your mechanism to get the private and public key
        let publicKey = loadKeyStringFromKeyChainAsNSData(PUBLIC_KEY + keyId)
        let privateKey = loadKeySecKeyFromKeyChain(PRIVATE_KEY + keyId)

        //SCCSR from ios-csr library
        let sccsr : SCCSR = SCCSR()

        sccsr.commonName = CN;
        sccsr.organizationName = O;
        sccsr.countryName = C;

        //    // aditional data you can set
        //    sccsr.countryName = @"";
        //    sccsr.organizationalUnitName = @"";
        //    sccsr.subjectDER = nil;
        //    //
        //
        let certificateRequest = sccsr.build(publicKey, privateKey: privateKey)
        let certificateRequestB64 = certificateRequest.base64EncodedStringWithOptions(NSDataBase64EncodingOptions())

        let certificateRequestPEM =
            "-----BEGIN CERTIFICATE REQUEST-----\\n" + certificateRequestB64 + "\\n-----END CERTIFICATE REQUEST-----\\n"

        return certificateRequestPEM

    }

After this, you can send the CSR to server in DER (format) or encode in PEM format (base64) depending of the capabilities of your serv

I guess you are missed the final step, returning the X509 from server to device to be stored

EDITED

Generating keys with KeyChain

I include also the code to help generating keys using iOS-KeyChain

let KEY_SIZE = 2048
let PUBLIC_KEY = "mysystem.publickey."
let PRIVATE_KEY = "mysystem.privatekey."

 // Generates a key pair in KeyChain using keyId as identifier and returns the public key
 // publicKey -->PUBLIC_KEY + keyId 
 // privateKey --> PRIVATE_KEY + keyId
 // KEY_SIZE is stablished globally
func generateKeyPairInKeyChain(keyId: String) -> String {

    let privateAttributes = [String(kSecAttrIsPermanent): true,
        String(kSecAttrApplicationTag): PRIVATE_KEY + keyId,
        String(kSecAttrIsPermanent): kCFBooleanTrue]
    let publicAttributes = [String(kSecAttrIsPermanent): true,
        String(kSecAttrApplicationTag): PUBLIC_KEY + keyId,
        String(kSecAttrIsPermanent): kCFBooleanTrue]

    let pairAttributes = [String(kSecAttrKeyType): kSecAttrKeyTypeRSA,
        String(kSecAttrKeySizeInBits): KEY_SIZE,
        String(kSecPublicKeyAttrs): publicAttributes,
        String(kSecPrivateKeyAttrs): privateAttributes]

    //Ensure that keychain has no key with keyId identifier
    deleteKeyPairFromKeyChain(keyId)

    var publicRef: SecKeyRef?
    var privateRef: SecKeyRef?

    //Generate the keys and recover the references in publicRef and privateRf
    switch SecKeyGeneratePair(pairAttributes, &publicRef, &privateRef) {
        case noErr:
            // Get the public key as a String
            let publicKeyStr = loadKeyStringFromKeyChain(PUBLIC_KEY + keyId)
            return publicKeyStr
        default:
            return ""
    }
}

Utilities

The following includes the utility functions used generating CSR or keys. You will see that are basically the same changing the type of result (some extra work is needed to simplify...)

//Delete an existing keypair from keychain (public + private)
func deleteKeyPairFromKeyChain(keyId: String) {
    deleteRSAKeyFromKeychain(PRIVATE_KEY + keyId)
    deleteRSAKeyFromKeychain(PUBLIC_KEY + keyId)
}

// Delete existing RSA key from keychain
private func deleteRSAKeyFromKeychain(tagName: String) {
    let queryFilter: [String: AnyObject] = [
        String(kSecClass)             : kSecClassKey,
        String(kSecAttrKeyType)       : kSecAttrKeyTypeRSA,
        String(kSecAttrApplicationTag): tagName
    ]
    let status: OSStatus = SecItemDelete(queryFilter)
    NSLog("private or public deletion result is: " + status.description)
}

// Finds the SecKeyRef corresponding to the parameter key and returns it as a String
private func loadKeyStringFromKeyChain(key: String) -> String {
    let query: Dictionary<String, AnyObject> = [
        String(kSecAttrKeyType): kSecAttrKeyTypeRSA,
        String(kSecAttrKeySizeInBits): KEY_SIZE,
        String(kSecClass): kSecClassKey,
        String(kSecAttrApplicationTag): key,
        kSecReturnData as String : kCFBooleanTrue ]

    var dataTypeRef: AnyObject? = nil
    var resultData: NSData? = nil

    let status: OSStatus = withUnsafeMutablePointer(&dataTypeRef) { SecItemCopyMatching(query as NSDictionary, UnsafeMutablePointer($0)) }
    NSLog("SecItemCopyMatching: " + status.description)

    if status == errSecSuccess {
        NSLog("private or public debug description is: " + dataTypeRef.debugDescription)
        resultData = dataTypeRef as? NSData
        let resultStr = resultData?.base64EncodedStringWithOptions([])
        NSLog("private or public String is: " + resultStr!)
        return resultStr!
    } else {
        NSLog("no key found!!!!")
        return ""
    }
}

// Finds the SecKeyRef corresponding to the parameter key and returns it
func loadKeySecKeyFromKeyChain(key: String) -> SecKeyRef {
    let query: Dictionary<String, AnyObject> = [
        String(kSecAttrKeyType): kSecAttrKeyTypeRSA,
        String(kSecAttrKeySizeInBits): KEY_SIZE,
        String(kSecClass): kSecClassKey,
        String(kSecAttrApplicationTag): key,
        kSecReturnRef as String : kCFBooleanTrue ]

    var dataTypeRef: Unmanaged<AnyObject>? = nil
    var resultData: SecKeyRef? = nil

    let status: OSStatus = withUnsafeMutablePointer(&dataTypeRef) { SecItemCopyMatching(query as NSDictionary, UnsafeMutablePointer($0)) }
    NSLog("SecItemCopyMatching: " + status.description)

    if status == errSecSuccess {
        NSLog("private or public debug description is: " + dataTypeRef.debugDescription)
        resultData = (dataTypeRef!.takeRetainedValue() as! SecKeyRef)
        NSLog("SecItemCopyMatching returns SecKey: " + resultData.debugDescription)
        return resultData!
    } else {
        return resultData!
    }
}

// Finds the SecKeyRef corresponding to the parameter key and returns it as a NSData
private func loadKeyStringFromKeyChainAsNSData(key: String) -> NSData {
    let query: Dictionary<String, AnyObject> = [
        String(kSecAttrKeyType): kSecAttrKeyTypeRSA,
        String(kSecAttrKeySizeInBits): KEY_SIZE,
        String(kSecClass): kSecClassKey,
        String(kSecAttrApplicationTag): key,
        kSecReturnData as String : kCFBooleanTrue ]

    var dataTypeRef: AnyObject? = nil
    var resultData: NSData? = nil

    let status: OSStatus = withUnsafeMutablePointer(&dataTypeRef) { SecItemCopyMatching(query as NSDictionary, UnsafeMutablePointer($0)) }
    NSLog("SecItemCopyMatching: " + status.description)

    if status == errSecSuccess {
        NSLog("private or public debug description is: " + dataTypeRef.debugDescription)
        resultData = dataTypeRef as? NSData
        return resultData!
    } else {
        NSLog("no key found!!!!")
        return resultData!
    }
}

EDITED

Export public key as DER

Note that this code will not provide the publickey in a readable format like DER

let publicKey = loadKeyStringFromKeyChainAsNSData(PUBLIC_KEY + keyId)

If you need to use the public key outside iOS (or importing a certificate and obtaining a valid key), extra actions are needed. Converting keys are supported using CryptoExportImportManager.swift of the following project

https://github.com/DigitalLeaves/CryptoExportImportManager

For example

func exportPublicKeyToDER(keyId:String) -> NSData?{

    let publicKey = loadKeyStringFromKeyChainAsNSData(PUBLIC_KEY + keyId)
    let keyType = kSecAttrKeyTypeRSA
    let keySize = 2048
    let exportImportManager = CryptoExportImportManager()
    if let exportableDERKey = exportImportManager.exportPublicKeyToDER(publicKey, keyType: keyType as String, keySize: keySize) {
        return exportableDERKey
    } else {
        return nil
    }
}
Comments