Generate a CSR in iOS Library? [closed]

2020-02-10 10:31发布

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

标签: ios ssl csr
1条回答
Summer. ? 凉城
2楼-- · 2020-02-10 11:02

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
    }
}
查看更多
登录 后发表回答