p12 (PKCS#12) certificat convert to DER fails using OpenSSL - ios

Dose somebody had a luck converting p12 crt to DER? What I am doing wrong?
Convert p12 to DER I am using i2d_PKCS12_bio. To get ref to certificate I am using SecCertificateCreateWithData() method which returns nil. This method is taking only DER data as written in the Apple doc.
In OpenSSL documentations is written:
i2d is the standard ASN1 function that converts the internal
structure into the DER representation: for example
i2d_ASN1_IA5STRING().
PKCS12 *p12; // p12 was created ok.
char *data_p12 = NULL;
BIO *mem = BIO_new(BIO_s_mem());
i2d_PKCS12_bio(mem, p12); // In the documentation is written that data should be converted to DER.
int _flush __attribute__((unused)) = BIO_flush(mem);
long size = BIO_get_mem_data(mem, &data_p12);
NSData *retData = [[NSData alloc] initWithBytes:data_p12 length:size];
CFDataRef dataRef = CFDataCreate(nil, retData.bytes, retData.length);
SecCertificateRef secRef = SecCertificateCreateWithData(nil, dataRef);
// secRef is nil. FAIL!
PS: I did save p12 data as file and it work as p12 file.

Related

How to Decoding AES-128 Data in iOS

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.

How to use SecPKCS12Import with private key encrypted with AES_256

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?

How to construct Data/NSData from UnsafeMutablePointer<T>

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
}

Generate a P12 file Xcode?

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()
}

How can I read and decrypt the file in iOS if it is an XML file and trying to store it in NSString to decrypt it?

NSString* pathss = [[NSBundle mainBundle] pathForResource:#"documentary" ofType:#"xml"];
NSString* contents = [NSString stringWithContentsOfFile:pathss encoding:NSUTF8StringEncoding error:NULL];
NSLog(#"content of file is: %#",contents);
It displays only the word "U", at the place of contents.
For a comment by the OP: "it is AES encrypted xml file"
If the XML file is AES encrypted it is comprised of data bytes, not characters and can not be represented as a UTF-8 string nor any string encoding in a useful way.
It is necessary to read the file into an NSData, not an NSString. AES decryption expects data, not a string as input and returns data as output. Since the original input to AES was a valid XML string (as data) the resultant output data can then be decoded to the original XML string.
if (pathss == nil) {
NSLog(#"pathss is nil");
// Handle error
}
NSError *error;
NSData *encryptedXML = [NSData dataWithContentsOfFile: pathss options:0 error:&error];
if (encryptedXML == nil) {
NSLog(#"Data read error: %#", error);
// Handle error
}
else {
// decrypt encryptedXML
}
Don't ignore the error handling and the error parameter, it will provide information about any error.
Decrypting the encryptedXML will require the encryption key, the encryption mode , padding option and possibly an iv (initialization vector).

Resources