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
Yes, it is possible but is not simple at all because iOS do not work with standard formats for keys as you could think
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
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 ""
}
}
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!
}
}
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
}
}