Search code examples
iossslcsr

Generate a CSR in iOS Library?


I want to see if is possible to generate a CSR (Certificate Signing Request) in iOS, and if there is a library for it. I want to generate a request, sign it with a private key from an extension, and then send the CSR request back to the server.

Is this possible, and is there a good library for it?

Thanks


Solution

  • 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
        }
    }