Import PEM encoded X.509 certificate into iOS KeyChain - ios

I'm receiving a String containing a PEM encoded X.509 certificate from somewhere. I'd like to import this certificate into the KeyChain of iOS.
I'm planning to do the following:
convert NSString to openssl X509
create PKCS12
convert PKCS12 to NSData
import NSData with SecPKCS12Import
So far I came up with the following code:
const char *cert_chars = [certStr cStringUsingEncoding:NSUTF8StringEncoding];
BIO *buffer = BIO_new(BIO_s_mem());
BIO_puts(buffer, cert_chars);
X509 *cert;
cert = PEM_read_bio_X509(buffer, NULL, 0, NULL);
if (cert == NULL) {
NSLog(#"error");
}
X509_print_fp(stdout, cert);
EVP_PKEY *privateKey;
const unsigned char *privateBits = (unsigned char *) [privateKeyData bytes];
int privateLength = [privateKeyData length];
privateKey = d2i_AutoPrivateKey(NULL, &privateBits, privateLength);
if (!X509_check_private_key(cert, privateKey)) {
NSLog(#"PK error");
}
PKCS12 *p12 = PKCS12_create("test", "David's Cert", privateKey, cert, NULL, 0, 0, 0, 0, 0);
Unfortunately, p12 is nil even though X509_check_private_key was successful and X509_print_fp(stdout, cert) prints a valid certificate.
is my approach correct
how come PKCS12_create seems to fail?
Update:
The call PKCS12_create seems to fail in the following method:
int EVP_PBE_CipherInit(ASN1_OBJECT *pbe_obj, const char *pass, int passlen,
ASN1_TYPE *param, EVP_CIPHER_CTX *ctx, int en_de)
{
const EVP_CIPHER *cipher;
const EVP_MD *md;
int cipher_nid, md_nid;
EVP_PBE_KEYGEN *keygen;
if (!EVP_PBE_find(EVP_PBE_TYPE_OUTER, OBJ_obj2nid(pbe_obj),
&cipher_nid, &md_nid, &keygen))
{
char obj_tmp[80];
EVPerr(EVP_F_EVP_PBE_CIPHERINIT,EVP_R_UNKNOWN_PBE_ALGORITHM);
if (!pbe_obj) BUF_strlcpy (obj_tmp, "NULL", sizeof obj_tmp);
else i2t_ASN1_OBJECT(obj_tmp, sizeof obj_tmp, pbe_obj);
ERR_add_error_data(2, "TYPE=", obj_tmp);
return 0;
}
if(!pass)
passlen = 0;
else if (passlen == -1)
passlen = strlen(pass);
if (cipher_nid == -1)
cipher = NULL;
else
{
cipher = EVP_get_cipherbynid(cipher_nid);
if (!cipher)
{
EVPerr(EVP_F_EVP_PBE_CIPHERINIT,EVP_R_UNKNOWN_CIPHER);
return 0;
}
}
if (md_nid == -1)
md = NULL;
else
{
md = EVP_get_digestbynid(md_nid);
if (!md)
{
EVPerr(EVP_F_EVP_PBE_CIPHERINIT,EVP_R_UNKNOWN_DIGEST);
return 0;
}
}
if (!keygen(ctx, pass, passlen, param, cipher, md, en_de))
{
EVPerr(EVP_F_EVP_PBE_CIPHERINIT,EVP_R_KEYGEN_FAILURE);
return 0;
}
return 1;
}
Retrieving the cipher
cipher = EVP_get_cipherbynid(cipher_nid);
somehow returns nil for "RC2-40-CBC".

The following calls were missing before creating the PKCS12:
OpenSSL_add_all_algorithms();
OpenSSL_add_all_ciphers();
OpenSSL_add_all_digests();
These solved the problems with the missing cipher and also a subsequent problem of a missing digest.

Related

S/MIME email encryption library for iOS

I am trying to find a S/MIME email encryption library for an iOS email app I am creating in Swift. I have been having trouble trying to find a library for the encryption, has anyone had any experience with this?
I have tried OpenSSL but have run into issues with importing all the files in need in the bridging header, for example I need to use functions in pem.h but if I try import pem.h the bridging header fails to be imported altogether.
Any help with this would be greatly appreciated.
I had a similar requirement. Eventually I had to import openSSL and write my own code to handle the decrypt of PKCS7. I made a small github repo which should help
https://github.com/zkrige/iOS-pkcs7-decrypt
here is the gist of the code
#include <openssl/bio.h>
#include <openssl/cms.h>
#include <openssl/err.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/crypto.h>
#include <openssl/rand.h>
X509 *getCert(const char *certificate) {
BIO *membuf = BIO_new(BIO_s_mem());
BIO_puts(membuf, certificate);
X509 *x509 = PEM_read_bio_X509(membuf, NULL, NULL, NULL);
return x509;
}
EVP_PKEY *getKey(const char *privateKey) {
BIO *membuf = BIO_new(BIO_s_mem());
BIO_puts(membuf, privateKey);
EVP_PKEY *key = PEM_read_bio_PrivateKey(membuf, NULL, 0, NULL);
return key;
}
PKCS7 *getContainer(const char *encrypted) {
BIO* membuf = BIO_new(BIO_s_mem());
BIO_set_mem_eof_return(membuf, 0);
BIO_puts(membuf, encrypted);
PKCS7* pkcs7 = SMIME_read_PKCS7(membuf, NULL);
if (!pkcs7) {
fprintf(stderr, "error: %ld\n", ERR_get_error());
}
return pkcs7;
}
char *decrypt(PKCS7 *pkcs7, EVP_PKEY *pkey, X509 *cert) {
BIO *out = BIO_new(BIO_s_mem());
if (PKCS7_decrypt(pkcs7, pkey, cert, out, 0) != 1) {
X509_free(cert);
EVP_PKEY_free(pkey);
PKCS7_free(pkcs7);
fprintf(stderr, "Error decrypting PKCS#7 object: %ld\n", ERR_get_error());
return NULL;
}
BUF_MEM* mem;
BIO_get_mem_ptr(out, &mem);
char *data = malloc(mem->length + 1);
memcpy(data, mem->data, mem->length + 1);
BIO_flush(out);
BIO_free(out);
return data;
}
char *decrypt_smime(const char *encrypted, const char *privateKey, const char *certificate) {
OpenSSL_add_all_algorithms();
ERR_load_crypto_strings();
X509 *cert = getCert(certificate);
if (!cert) {
return NULL;
}
EVP_PKEY *pkey = getKey(privateKey);
if (!pkey) {
X509_free(cert);
return NULL;
}
PKCS7 *pkcs7 = getContainer(encrypted);
if (!pkcs7) {
X509_free(cert);
EVP_PKEY_free(pkey);
return NULL;
}
char *data = decrypt(pkcs7, pkey, cert);
X509_free(cert);
EVP_PKEY_free(pkey);
PKCS7_free(pkcs7);
return data;
}

d2i_ECParameters Returns Null

I want to create a EC_Key from NSData, via OpenSSL. So I write the following:
- (void)setPrivateKey:(NSData *)privateKey {
const unsigned char *bits = (unsigned char *) [privateKey bytes];
eckey = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
ec_group = EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1);
EC_KEY_set_group(eckey, ec_group);
EC_KEY_generate_key(eckey);
EC_KEY_set_asn1_flag(eckey, OPENSSL_EC_NAMED_CURVE);
eckey = d2i_ECParameters(&eckey, &bits, privateKey.length);
}
but
eckey = d2i_ECParameters(&eckey, &bits, privateKey.length);
returns null.
What is the problem?
Thanks to #jww for his great comments, finally, I succeed to solve this problem by the following code block
- (void)setPrivateKey:(NSData *)privateKey {
const unsigned char *privateKeyBits = (unsigned char *) [privateKey bytes];
ec_group = EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1);
eckey = EC_KEY_new();
EC_KEY_set_group(eckey, ec_group);
EC_KEY_generate_key(eckey);
EC_KEY_set_asn1_flag(eckey, OPENSSL_EC_NAMED_CURVE);
BIGNUM *prv = BN_bin2bn(privateKeyBits, sizeof(privateKeyBits), NULL);
EC_KEY_set_private_key(eckey, prv);
EC_POINT *pub = EC_POINT_new(ec_group);
EC_POINT_mul(ec_group, pub, prv, NULL, NULL, NULL);
EC_KEY_set_public_key(eckey, pub);
pkey = EVP_PKEY_new();
EVP_PKEY_set1_EC_KEY(pkey, eckey);
}
Although I had both public key and private key, but with above code I calculated the public key from private key.

One function gives several results in swift

I have a method in objective-C which I call from swift. It worked pretty well in swift 2, but in swift 3 the behaviour has changed. It gives me 3 different results, even though I send the same parameters.
Sometimes it doesnt find pfile, sometimes it fails on pin checking, sometimes works good and gives me x509.
char* ParsePKCS12(unsigned char* pkcs12_path, unsigned char * pin) {
printf("PARSE PATH: %s\n", pkcs12_path);
printf("PASSWORD: %s\n", pin);
NSString *pfile = [NSString stringWithUTF8String:pkcs12_path];
FILE *fp;
PKCS12 *p12;
EVP_PKEY *pkey;
X509 *cert;
BIO *databio = BIO_new(BIO_s_mem());
STACK_OF(X509) *ca = NULL;
if([[NSFileManager defaultManager] fileExistsAtPath:pfile]) {
NSLog(#"ok, pfile exists!");
} else {
NSLog(#"error, pfile does not exists!");
return "-1";
}
OpenSSL_add_all_algorithms();
ERR_load_crypto_strings();
fp = fopen([pfile UTF8String], "rb");
p12 = d2i_PKCS12_fp(fp, NULL);
fclose (fp);
if (!p12) {
fprintf(stderr, "Error reading PKCS#12 file\n");
ERR_print_errors_fp(stderr);
return "-1";
}
if (!PKCS12_parse(p12, (const char *)pin, &pkey, &cert, &ca)) { //Error at parsing or pin error
fprintf(stderr, "Error parsing PKCS#12 file\n");
ERR_print_errors_fp(stderr);
ERR_print_errors(databio);
return "-1";
}
BIO *bio = NULL;
char *pem = NULL;
if (NULL == cert) {
//return NULL;
return "-1";
}
bio = BIO_new(BIO_s_mem());
if (NULL == bio) {
return "-1";
}
if (0 == PEM_write_bio_X509(bio, cert)) {
BIO_free(bio);
//return NULL;
}
pem = (char *) malloc(bio->num_write + 1);
if (NULL == pem) {
BIO_free(bio);
return "-1";
}
memset(pem, 0, bio->num_write + 1);
BIO_read(bio, pem, bio->num_write);
BIO_free(bio);
PKCS12_free(p12);
return pem;
}
this code I call in swift like this:
self.x509 = String(cString:ParsePKCS12(UnsafeMutablePointer<UInt8>(mutating: self.path),
UnsafeMutablePointer<UInt8>(mutating: "123456"))!)
Your call
self.x509 = String(cString:ParsePKCS12(UnsafeMutablePointer<UInt8>(mutating: self.path),
UnsafeMutablePointer<UInt8>(mutating: "123456"))!)
does not work reliably because in both
UnsafeMutablePointer<UInt8>(mutating: someSwiftString)
calls, the compiler creates a temporary C string representation of
the Swift string and passes that to the function. But that C string
is only valid until the UnsafeMutablePointer constructor returns, which means that the second
string conversion can overwrite the first, or any other undefined
behaviour.
The simplest solution would be to change the C function to
take constant C strings (and use the default signedness):
char* ParsePKCS12(const char * pkcs12_path, const char * pin)
Then you can simply call it as
self.x509 = String(cString: ParsePKCS12(self.path, "123456"))
and the compiler creates temporary C strings which are valid during
the call of ParsePKCS12().

Decoding multiple DER certificates on iOS

I've got a CFDataRef that contains a DER-encoded X.509 certificate that I can use to create a SecCertificateRef like so:
CFDataRef binaryDataRef = ... // from third party
SecCertificateRef certRef = SecCertificateCreateWithData (NULL, binaryDataRef);
However in some cases my CFData can contain multiple certs (a cert chain) that have been concatenated together using i2d_X509 by third-party code.
Is there a call on iOS similar to SecCertificateCreateWithData that can decode all of the certificates? SecCertificateCreateWithData is just giving me the first cert.
I figured it out - I can use d2i_X509 to figure out where each cert is, and it handily adjusts the pointer to the next one in the array.
CFArrayRef nmCopyEncodedCertificates(CFDataRef derDataRef) {
CFMutableArrayRef certs = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
if (derDataRef) {
CFIndex bytesRemaining = CFDataGetLength(derDataRef);
const unsigned char *pDerData = CFDataGetBytePtr(derDataRef);
const unsigned char *pCurCertBegin = pDerData;
X509 *certX509 = NULL;
while ((certX509 = d2i_X509(NULL, &pDerData, bytesRemaining)) != NULL && // increments pDerData to next cert
bytesRemaining > 0) {
X509_free(certX509);
long len = pDerData - pCurCertBegin;
if (len > 0) {
CFDataRef certData = CFDataCreate(kCFAllocatorDefault, pCurCertBegin, len);
if (certData) {
SecCertificateRef certRef = SecCertificateCreateWithData (kCFAllocatorDefault, certData);
if (certRef) {
CFArrayAppendValue(certs, certRef);
CFRelease(certRef);
}
CFRelease(certData);
}
bytesRemaining -= len;
pCurCertBegin = pDerData;
} else {
break;
}
}
}
return certs;
}

How to load certificates in tls connection using gcdasyncsocket

I am working on an iPhone app that works on GcdAsyncSocket and creates TLS connection, I generate RSA keys and CSR using those and sent CSR to server, server responded with a certificate and some other certificate that is like public key to it. Now I need to make another TLS connection with server and send private key nd 2 certificates back to it. I have gone through many posts but didn't find any way how to achieve this.
If anyone could help and with some code that would be great help.
Thanks.
After spending good amount of time, I was able to resolve the issues using open SSL library. I used following code
+(PKCS12*)convertToP12Certificate:(NSString*)certificate
certificateChain:(NSArray*)certificateChain
publicCertificate:(NSString*) publicCertificate
andPrivateKey:(NSString*)privateKey
{
//we create a x509 from primary certificate which goes as a single entity when creating p12
const char *cert_chars = [certificate cStringUsingEncoding:NSUTF8StringEncoding];
BIO *buffer = BIO_new(BIO_s_mem());
BIO_puts(buffer, cert_chars);
X509 *cert;
cert = PEM_read_bio_X509(buffer, NULL, 0, NULL);
if (cert == NULL) {
NSLog(#"error");
}
X509_print_fp(stdout, cert);
//create a evp from private key which goes as a separate entity while creating p12
const char *privateKey_chars = [privateKey cStringUsingEncoding:NSUTF8StringEncoding];
BIO *privateKeyBuffer = BIO_new(BIO_s_mem());
BIO_puts(privateKeyBuffer, privateKey_chars);
EVP_PKEY *evp;
evp =PEM_read_bio_PrivateKey(privateKeyBuffer, NULL, NULL, "Enc Key");
if (evp == NULL) {
NSLog(#"error");
}
if (!X509_check_private_key(cert, evp))
{
NSLog(#"PK error");
}
PKCS12 *p12;
SSLeay_add_all_algorithms();
ERR_load_crypto_strings();
const char *cert_chars2 = [publicCertificate cStringUsingEncoding:NSUTF8StringEncoding];
BIO *buffer2= BIO_new(BIO_s_mem());
BIO_puts(buffer2, cert_chars2);
X509 *cert2;
cert2 = PEM_read_bio_X509(buffer2, NULL, 0, NULL);
if (cert2 == NULL) {
NSLog(#"error");
}
X509_print_fp(stdout, cert2);
STACK_OF(X509) *sk = sk_X509_new_null();
sk_X509_push(sk, cert2);
for(NSString * tempCertificate in certificateChain)
{
const char *cert_chars3 = [tempCertificate cStringUsingEncoding:NSUTF8StringEncoding];
BIO *buffer3= BIO_new(BIO_s_mem());
BIO_puts(buffer3, cert_chars3);
X509 *cert3;
cert3 = PEM_read_bio_X509(buffer3, NULL, 0, NULL);
if (cert3 == NULL) {
NSLog(#"error");
}
X509_print_fp(stdout, cert3);
sk_X509_push(sk, cert3);
}
p12 = PKCS12_create(P12_Password, P12_Name, evp, cert, sk, 0,0,0,0,0);
return p12;
}
+(NSArray*)getCertificateChainForCertificate:(NSString*)certificate
certificateChain:(NSArray*)certificateChain
publicCertificate:(NSString*) publicCertificate
andPrivateKey:(NSString*)privateKey
{
PKCS12 *p12 = [CryptoHelper convertToP12Certificate:certificate
certificateChain:certificateChain
publicCertificate: publicCertificate
andPrivateKey:privateKey];
NSData *PKCS12Data = [CryptoHelper convertP12ToData:p12];
NSArray *certs = nil;
SecIdentityRef identityRef = nil;
CFDataRef inPKCS12Data = (__bridge CFDataRef)PKCS12Data;
CFStringRef password = CFSTR(P12_Password);
const void *keys[] = { kSecImportExportPassphrase };
const void *values[] = { password };
CFDictionaryRef options = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
OSStatus securityError = SecPKCS12Import(inPKCS12Data, options, &items);
CFRelease(options);
CFRelease(password);
if (securityError == errSecSuccess)
{
NSLog(#"Success opening p12 certificate. Items: %ld", CFArrayGetCount(items));
CFDictionaryRef identityDict = CFArrayGetValueAtIndex(items, 0);
identityRef = (SecIdentityRef)CFDictionaryGetValue(identityDict, kSecImportItemIdentity);
if(certificateChain)
{
CFArrayRef certificates = (CFArrayRef)CFDictionaryGetValue(identityDict,kSecImportItemCertChain);
// There are 3 items in array when we retrieve certChain and for TLS connection cert
SecIdentityRef chainIdentity = (SecIdentityRef)CFArrayGetValueAtIndex(certificates,1);
certs = [[NSArray alloc] initWithObjects:(__bridge id)identityRef,(__bridge id)chainIdentity, nil];
}
else
{
certs = [[NSArray alloc] initWithObjects:(__bridge id)identityRef, nil];
}
} else
{
NSLog(#"Error opening Certificate.");
}
return certs;
}
We can pass this array to TLS connection for key GCDAsyncSocketSSLCertificates.

Resources