I am generating a PKCS12 file using OpenSSL library in Swift. Following is the code that I use to generate the file:
guard let p12 = PKCS12_create(passPhrase, name, privateKey, certificate, nil, NID_aes_256_cbc, 0, 0, 0, 0) else {
ERR_print_errors_fp(stderr)
return
}
Please note that I am using NID_aes_256_cbc to encrypt the private key.
Then when I use the following code to import the p12 file in swift, I had the error code of -26275.
var items: CFArray?
let certOptions: NSDictionary = [kSecImportExportPassphrase as NSString: passwordStr as NSString]
self.securityError = SecPKCS12Import(p12Data as NSData, certOptions, &items)
print(securityError!.description)
After consulting OSStatus for error code explanation, I found that it's a errSecInvalidKey.
If I change the parameter from NID_aes_256_cbc back to 0, which uses the default 3DES algorithm, everything works fine.
So I was wondering if SecPKCS12Import decrypting the private key with 3DES algorithm by default. If yes, how should I make SecPKCS12Import to decrypt the AES encrypted private key?
Related
I'm trying to create an RSA key for signing data in my Xamarin.iOS app on a physical device. Below is my code
using (var access = new SecAccessControl(SecAccessible.WhenUnlockedThisDeviceOnly, SecAccessControlCreateFlags.BiometryCurrentSet|SecAccessControlCreateFlags.PrivateKeyUsage))
{
var keyParameters = new SecKeyGenerationParameters
{
KeyType = SecKeyType.RSA,
KeySizeInBits = 2048,
Label = AppInfo.PackageName,
TokenID = SecTokenID.SecureEnclave,
PrivateKeyAttrs = new SecKeyParameters
{
IsPermanent = true,
ApplicationTag = NSData.FromString(AppInfo.PackageName, NSStringEncoding.UTF8),
AccessControl = access,
CanSign = true,
CanVerify = true
}
};
var privateKey = SecKey.CreateRandomKey(keyParameters.Dictionary, out NSError nsError);
var publicKey = privateKey.GetPublicKey();
NSData keyData = publicKey.GetExternalRepresentation();
}
the above code is giving below NSError and null private key
{The operation couldn’t be completed. (OSStatus error -50 - Key generation failed, error -50)}.
The above code is working fine without SecAccessControlCreateFlags.PrivateKeyUsage and TokenID = SecTokenID.SecureEnclave
Please let me know how to resolve this.
The Secure Enclave does not support RSA keys. It can only create elliptic curve keys with curve P256 (aka NIST P-256 or secp256r1). If you want to use RSA, you're left with the default Security implementation (without the Secure Enclave flag).
If you insist on using the secure enclave, you'll have to use the above mentioned curve.
I am exchanging data with a device that requires encryption. According to the instructions, my iOS app generates an RSA 2048-bit public-private key pair and I send the device the public key.
I created the public-private key pair using a macOS command line with openssl shown here (as I cannot figure out how to make iOS do it - but that's a future question):
openssl req -x509 -newkey rsa:2048 -out cert.pem
openssl x509 -in cert.pem -noout -pubkey
I copied the values from the cert.pem and the private and public keys into my app:
let certificate = "MIIDej....GWB"
let privateKey = "MIIFhz...Q=="
let publicKey = "MIIBI...AQAB"
When I send the device the public key, the device is happy and sends back data in AES-128 encrypted using the public key I sent.
I am in over my head here, but I'm the one trying to get this device to work.
Is there a way to take the private key or cert that I have set as Strings in my app to decode the data coming from the device?
I have been looking at the Security and CryptoKit docs and it makes my head swim.
FWIW, I was able to use SecCertificateCreateWithData on the String set from the cert.pem file and read some of its info like the summary. But I have no idea how to apply the private key or use the cert to decode the data. I need like a Dummies cliff notes or something.
Thank you.
Apple documentation describe how to generate key pair on the iOS side.
Example:
let tag = "com.example.keys.mykey".data(using: .utf8)!
let attributes: [String: Any] =
[kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecAttrKeySizeInBits as String: 2048,
kSecPrivateKeyAttrs as String:
[kSecAttrIsPermanent as String: true,
kSecAttrApplicationTag as String: tag]
]
var publicKey: SecKey?
var privateKey: SecKey?
SecKeyGeneratePair(attributes, &publicKey, &privateKey)
More attributes are available here.
And then you can get External representations of private or public key using method:
SecKeyCopyExternalRepresentation.
Example:
var error: Unmanaged<CFError>?
SecKeyCopyExternalRepresentation(secKey, &error) as Data?
To decrypt AES-128 data you need to use CCCrypt. Here you have example from Apple support how the data is encrypted, but to decrypt data you need to change first argument to kCCDecrypt.
Example:
extern NSData * SecDecryptAES128CBCPad(NSData * data, NSData * key, NSData * iv) {
CCCryptorStatus err;
NSMutableData * result;
size_t resultLength;
NSCParameterAssert(key.length == kCCKeySizeAES128);
NSCParameterAssert(iv.length == kCCBlockSizeAES128);
// Padding can expand the data, so we have to allocate space for that. The rule for block
// cyphers, like AES, is that the padding only adds space on encryption (on decryption it
// can reduce space, obviously, but we don't need to account for that) and it will only add
// at most one block size worth of space.
result = [[NSMutableData alloc] initWithLength:[data length] + kCCBlockSizeAES128];
err = CCCrypt(
kCCDecrypt,
kCCAlgorithmAES128,
kCCOptionPKCS7Padding,
key.bytes, key.length,
iv.bytes,
data.bytes, data.length,
result.mutableBytes, result.length,
&resultLength
);
assert(err == kCCSuccess);
// Set the output length to the value returned by CCCrypt. This is necessary because
// we have padding enabled, meaning that we might have allocated more space than we needed.
[result setLength:resultLength];
return result;
}
Arguments in CCCrypt method (like kCCOptionPKCS7Padding, iv) you need to adjust to your example.
Background: I am using OpenSSL library to create PKCS12 file in Swift. I have the certificate and private key stored in Keychain.
Problem: I am able to create a PKCS12 file and store it in the app bundle successfully. When I want to use the PKCS12 file (e.g. Receive a auth challenge from a HTTPS server), I can load the file by using SecPKCS12Import() function provided by Apple. Now, instead of generating a physical file, I want to generate the PKCS12 object in flight whenever I need it. It will be stored in memory. Since I am new to Swift, I am seeking help for converting from UnsafeMutablePointer to Data.
You will understand more when you read my following code:
Previously, I have my createP12 function implemented as:
createP12(pemCert: String, pemPK: String) {
// .......
// Code to load certificate and private key Object..
guard let p12 = PKCS12_create(passPhrase, name, privateKey, certificate, nil, NID_pbe_WithSHA1And3_Key_TripleDES_CBC, NID_pbe_WithSHA1And3_Key_TripleDES_CBC, 0, 0, 0) else {
ERR_print_errors_fp(stderr)
return
}
// Save p12 to file
let fileManager = FileManager.default
let tempDirectory = NSTemporaryDirectory() as NSString
let path = tempDirectory.appendingPathComponent("ssl.p12")
fileManager.createFile(atPath: path, contents: nil, attributes: nil)
guard let fileHandle = FileHandle(forWritingAtPath: path) else {
LogUtils.logError("Cannot open file handle: \(path)")
return
}
let p12File = fdopen(fileHandle.fileDescriptor, "w")
i2d_PKCS12_fp(p12File, p12)
fclose(p12File)
fileHandle.closeFile()
}
Then when I want to read the p12 file, I can call
let p12Data = NSData(contentsOfFile: Bundle.main.path(forResource: mainBundleResource, ofType:resourceType)!)! as Data
var items: CFArray?
let certOptions: NSDictionary = [kSecImportExportPassphrase as NSString: passwordStr as NSString]
self.securityError = SecPKCS12Import(p12Data as NSData, certOptions, &items)
// Code to read attributes
From createP12() function, I firstly got a p12 object in type of UnsafeMutablePointer<PKCS12> then store it in the file. Instead, now I want to pass p12 directly to the pkcs12 reader function. To do so, I have to firstly convert p12 object into a Data/NSData object since that's what required by SecPKCS12Import() function.
So, long story short, how can I construct a Data/NSData object from the p12 object which is in type of UnsafaMutablePointer<PKCS12> thus I can pass it into SecPKCS12Import()?
This should work (it compiles but I could not test it). The idea is to write the PKCS12 object to a memory buffer and then create Data
from the buffer:
func p12ToData(p12: UnsafeMutablePointer<PKCS12>) -> Data {
// Write PKCS12 to memory buffer:
let mbio = BIO_new(BIO_s_mem())
i2d_PKCS12_bio(mbio, p12)
// Get pointer to memory buffer and number of bytes. The
// # define BIO_get_mem_data(b,pp) BIO_ctrl(b,BIO_CTRL_INFO,0,(char *)pp)
// macro is not imported to Swift.
var ptr = UnsafeRawPointer(bitPattern: 1)!
let cnt = BIO_ctrl(mbio, BIO_CTRL_INFO, 1, &ptr)
// Create data from pointer and count:
let data = Data(bytes: ptr, count: cnt)
// Release memory buffer:
BIO_free(mbio)
return data
}
I know there is a function called SecPKCS12Import that allows you to import data from a p12 file. However, I want to go the reverse route. I have a SecCertificateRef and a public/private SecKeyRef, which I want to use to create a P12 file. Does anyone know how to do this on iPhone?
Thanks
Unfortunately, there CommonCrypto does not provide any means to export PKCS12 containers let alone any other export functionality (even though its OSX counterpart can do that). There are ways to extract the SecKeyRef raw data from the key chain but then you still need to write all the PKCS12 wrapping yourself.
We were facing a similar issue and went with OpenSSL.
Compiling OpenSSL for iOS
Integrating OpenSSL requires a bit of work as you need to compile and link the OpenSSL sources yourself. Fortunately, there are some build scripts available so you do not have to do that yourself, e.g, https://github.com/x2on/OpenSSL-for-iPhone . I suggest you use them as you need to patch some of the Makefiles which is a bit of a hazel. Those build scripts generate static linked libraries for both iOS and tvOS. You just need to link them against your project and set the Header and Library Search Path accordingly.
CocoaPods
You can also use the official OpenSSL CocoaPod . That saves you the trouble of configuring your project.
Exporting PKCS12
As you might know, OpenSSL is a C library. That means you might want to encapsulate all the C functions into a Objective-C or Swift wrapper. There are some open source wrappers that support im- and exporting PKCS12 containers but I have not found a single one with good documentation. You should be able to derive the relevant snippets from some of the sources though.
https://github.com/microsec/MscX509Common/blob/master/src/MscPKCS12.m
You can have a look at this example as well http://fm4dd.com/openssl/pkcs12test.htm .
Hope that helps!
I agree that this task can only be performed using OpenSSL. It is a bit tricky to compile it for iOS but with OpenSSL-for-iPhone it is quite possible.
To solve the given task of creating a PKCS12 keystore from a SecCertificate and a SecKey with Swift 3 just add the static libraries libssl.aand libcrypto.a to your project and create the following bridging header:
#import <openssl/err.h>
#import <openssl/pem.h>
#import <openssl/pkcs12.h>
#import <openssl/x509.h>
To create the keystore the input data have to be converted to OpenSSL data structures, which requires some creativity. The SecCertificate can be converted directly to DER format and then read into a X509 structure. The SecKey is even worse to handle. The only possible solution to get the data of the key is to write it to the keychain and get the reference. From the reference we can get the base 64 encoded string, which then can be read into a EVP_PKEY structure. Now, we can create the keystore and save it to a file. To access the data in the keystore via iOS functions we must read the file via let data = FileManager.default.contents(atPath: path)! as NSData
The full solution is shown in the following:
func createP12(secCertificate: SecCertificate, secPrivateKey: SecKey) {
// Read certificate
// Convert sec certificate to DER certificate
let derCertificate = SecCertificateCopyData(secCertificate)
// Create strange pointer to read DER certificate with OpenSSL
// data must be a two-dimensional array containing the pointer to the DER certificate as single element at position [0][0]
let certificatePointer = CFDataGetBytePtr(derCertificate)
let certificateLength = CFDataGetLength(derCertificate)
let certificateData = UnsafeMutablePointer<UnsafePointer<UInt8>?>.allocate(capacity: 1)
certificateData.pointee = certificatePointer
// Read DER certificate
let certificate = d2i_X509(nil, certificateData, certificateLength)
// Print certificate
X509_print_fp(stdout, certificate)
// Read private key
// Convert sec key to PEM key
let tempTag = "bundle.temp"
let tempAttributes = [
kSecClass: kSecClassKey,
kSecAttrApplicationTag: tempTag,
kSecAttrKeyType: kSecAttrKeyTypeRSA,
kSecValueRef: secPrivateKey,
kSecReturnData: kCFBooleanTrue
] as NSDictionary
var privateKeyRef: AnyObject?
// Store private key in keychain
SecItemDelete(tempAttributes)
guard SecItemAdd(tempAttributes, &privateKeyRef) == noErr else {
NSLog("Cannot store private key")
return
}
// Get private key data
guard let privateKeyData = privateKeyRef as? Data else {
NSLog("Cannot get private key data")
return
}
let pemPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\n\(privateKeyData.base64EncodedString())\n-----END RSA PRIVATE KEY-----\n"
// Delete private key in keychain
SecItemDelete(tempAttributes)
let privateKeyBuffer = BIO_new(BIO_s_mem())
pemPrivateKey.data(using: .utf8)!.withUnsafeBytes({ (bytes: UnsafePointer<Int8>) -> Void in
BIO_puts(privateKeyBuffer, bytes)
})
let privateKey = PEM_read_bio_PrivateKey(privateKeyBuffer, nil, nil, nil)
// !!! Remove in production: Print private key
PEM_write_PrivateKey(stdout, privateKey, nil, nil, 0, nil, nil)
// Check if private key matches certificate
guard X509_check_private_key(certificate, privateKey) == 1 else {
NSLog("Private key does not match certificate")
return
}
// Set OpenSSL parameters
OPENSSL_add_all_algorithms_noconf()
ERR_load_crypto_strings()
// Create P12 keystore
let passPhrase = UnsafeMutablePointer(mutating: ("" as NSString).utf8String)
let name = UnsafeMutablePointer(mutating: ("SSL Certificate" as NSString).utf8String)
guard let p12 = PKCS12_create(passPhrase, name, privateKey, certificate, nil, 0, 0, 0, 0, 0) else {
NSLog("Cannot create P12 keystore:")
ERR_print_errors_fp(stderr)
return
}
// Save P12 keystore
let fileManager = FileManager.default
let tempDirectory = NSTemporaryDirectory() as NSString
let path = tempDirectory.appendingPathComponent("ssl.p12")
fileManager.createFile(atPath: path, contents: nil, attributes: nil)
guard let fileHandle = FileHandle(forWritingAtPath: path) else {
NSLog("Cannot open file handle: \(path)")
return
}
let p12File = fdopen(fileHandle.fileDescriptor, "w")
i2d_PKCS12_fp(p12File, p12)
fclose(p12File)
fileHandle.closeFile()
}
I want to encrypt data using RSA , I tried to generate the key in my code and it's working , But what I actually need is to get the public key as a string from server and then use it as Seckey so I can use it to encrypt data using RSA,
I tried this code:
//KeyString is the string of the key from server
let KeyData = (keyString as NSString).dataUsingEncoding(NSUTF8StringEncoding) as NSData!
var cert : Unmanaged<SecCertificateRef>!;
var policy : Unmanaged<SecPolicy>!;
cert = SecCertificateCreateWithData(kCFAllocatorDefault, KeyData);
policy = SecPolicyCreateBasicX509();
var status : OSStatus = noErr
var trust: SecTrust?
var certArray : [Unmanaged<SecCertificateRef>!] = [cert];
var certArrayPointer = UnsafeMutablePointer<UnsafePointer<Void>>(certArray)
status = SecTrustCreateWithCertificates(cert, policy, trust);
let publicKey: SecKeyRef = SecTrustCopyPublicKey(trust!).takeUnretainedValue()
I couldn't run this code because SecTrustCreateWithCertificates Method is expecting certificate as anyObject! , I don't Know how to fix this,And if solving this will let me get the SecKey.
I got the code above from this answer in objective-c
So if any one can help me getting the right code to solve this , I will be very thankful :)
For mac:
let pubKey = "-----BEGIN PUBLIC KEY-----MIICIjANBgAgK.......InbFk1FkucQqruMyUCAwEAAQ==-----END PUBLIC KEY-----"
let pubKeyData = pubKey.dataUsingEncoding(NSASCIIStringEncoding)
var error: Unmanaged<CFErrorRef>?
let secKey = SecKeyCreateFromData(NSDictionary(), pubKeyData!, &error)
Where pubKey is a string representation of your public key.
If you don't know your public key, you can infer it from your private key with the following command:
openssl rsa -in server.key -pubout > mykey.pub
Where server.key is the file containing -----BEGIN RSA PRIVATE KEY-----
as the first line.
For iOS:
It's a bit more complicate.
You need a der file. It's a binary representation of your certificate.
If you need to convert an existing certificate, you can do so with the following command:
openssl x509 -outform der -in file.crt|pem -out mycert.der
The .crt or .pem file contains -----BEGIN CERTIFICATE----- as the first line.
Put the der file in your bundle and do:
let certificateData = NSData(contentsOfURL:NSBundle.mainBundle().URLForResource("mycert", withExtension: "der")!)
let certificate = SecCertificateCreateWithData(nil, certificateData!)
var trust: SecTrustRef?
let policy = SecPolicyCreateBasicX509()
let status = SecTrustCreateWithCertificates(certificate!, policy, &trust)
if status == errSecSuccess {
let key = SecTrustCopyPublicKey(trust!)!;
}
Yatta ! Key now contains a SecKey representation of your public key. Happy Pinning.
Here's how I did this:
let cert = SecCertificateCreateWithData(kCFAllocatorDefault, certData)?.takeRetainedValue()
if cert != nil {
var trust: Unmanaged<SecTrust>?
let policy = SecPolicyCreateBasicX509().takeRetainedValue()
let status = SecTrustCreateWithCertificates(cert, policy, &trust)
if status == errSecSuccess {
let trustRef = trust!.takeRetainedValue()
let key = SecTrustCopyPublicKey(trustRef)!.takeRetainedValue();
}
}
This works, but you need to make sure that what you pass to SecCertificateCreateWithData() is a DER-encoded certificate, and not just a DER-encoded key. You need a certificate signed by your server's private key to the get the associated public key.
I Did this used Alamofire:
private static func publicKeyForCertificate(certificate: SecCertificate) -> SecKey? {
var publicKey: SecKey?
var trust: Unmanaged<SecTrust>?
let policy = SecPolicyCreateBasicX509().takeRetainedValue()
let status = SecTrustCreateWithCertificates(certificate, policy, &trust)
if status == errSecSuccess {
let trustRef = trust!.takeRetainedValue()
publicKey = SecTrustCopyPublicKey(trustRef)!.takeRetainedValue()
}
return publicKey
}