Ios: How to Create Pkcs12 (P12) Keystore from Private Key and X509Certificate in Application Programmatically

iOS: How to create PKCS12 (P12) keystore from private key and x509certificate in application programmatically?

If you use openssl, you don't have to copy the full source code into your project, it is enough to add the libs and headers, so the openssl library can be used without any size problem.
You can generate a key and a cert like that with openssl:

EVP_PKEY * pkey;
pkey = EVP_PKEY_new();

RSA * rsa;
rsa = RSA_generate_key(
2048, /* number of bits for the key - 2048 is a sensible value */
RSA_F4, /* exponent - RSA_F4 is defined as 0x10001L */
NULL, /* callback - can be NULL if we aren't displaying progress */
NULL /* callback argument - not needed in this case */
);

EVP_PKEY_assign_RSA(pkey, rsa);

X509 * x509;
x509 = X509_new();

ASN1_INTEGER_set(X509_get_serialNumber(x509), 1);

X509_gmtime_adj(X509_get_notBefore(x509), 0);
X509_gmtime_adj(X509_get_notAfter(x509), 31536000L);

X509_set_pubkey(x509, pkey);

X509_NAME * name;
name = X509_get_subject_name(x509);

X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC,
(unsigned char *)"CA", -1, -1, 0);
X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC,
(unsigned char *)"MyCompany Inc.", -1, -1, 0);
X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC,
(unsigned char *)"localhost", -1, -1, 0);

X509_set_issuer_name(x509, name);

//X509_sign(x509, pkey, EVP_sha1());

const EVP_CIPHER *aConst = EVP_des_ede3_cbc();

And you can write this into pem format with these functions:

PEM_write_PrivateKey(f, pkey, NULL, NULL, 0, NULL, NULL);

PEM_write_X509(
f, /* write the certificate to the file we've opened */
x509 /* our certificate */
);

After that it is possible to write these files into a p12 file, source from here:
https://github.com/luvit/openssl/blob/master/openssl/demos/pkcs12/pkwrite.c

/* pkwrite.c */

#include <stdio.h>
#include <stdlib.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#include <openssl/pkcs12.h>

/* Simple PKCS#12 file creator */
int main(int argc, char **argv)
{
FILE *fp;
EVP_PKEY *pkey;
X509 *cert;
PKCS12 *p12;
if (argc != 5) {
fprintf(stderr, "Usage: pkwrite infile password name p12file\n");
exit(1);
}
SSLeay_add_all_algorithms();
ERR_load_crypto_strings();
if (!(fp = fopen(argv[1], "r"))) {
fprintf(stderr, "Error opening file %s\n", argv[1]);
exit(1);
}
cert = PEM_read_X509(fp, NULL, NULL, NULL);
rewind(fp);
pkey = PEM_read_PrivateKey(fp, NULL, NULL, NULL);
fclose(fp);
p12 = PKCS12_create(argv[2], argv[3], pkey, cert, NULL, 0,0,0,0,0);
if(!p12) {
fprintf(stderr, "Error creating PKCS#12 structure\n");
ERR_print_errors_fp(stderr);
exit(1);
}
if (!(fp = fopen(argv[4], "wb"))) {
fprintf(stderr, "Error opening file %s\n", argv[1]);
ERR_print_errors_fp(stderr);
exit(1);
}
i2d_PKCS12_fp(fp, p12);
PKCS12_free(p12);
fclose(fp);
return 0;
}

How can I create a PKCS12 p12 file with selfsigned certificate for DSA keypair in C#?

Got interested by this and here is my little investigation. When you set PrivateKey of certificateDSA, .NET code does roughly this:

byte[] numArray1 = ((ICspAsymmetricAlgorithm) certificateDSA.PublicKey.Key).ExportCspBlob(false);
byte[] numArray2 = csp.ExportCspBlob(false);
// And then those two blobs are compared byte by byte

Those blobs are different starting at position 420 (they have length of 444). So something is wrong in csp parameters. It's not easy to compare raw bytes, so let's convert them to readable xml with:

var xml1 = certificateDSA.PublicKey.Key.ToXmlString(false);
var xml2 = csp.ToXmlString(false);

What we will get is this:

<DSAKeyValue> <!--this is parameters of cert public key-->
<P>2arEQPD3/tKm7pJF1y4gN0/4WzSGfkgFwdmtmoUf/gHoXpdBetRH/5j98qo4k1ybePxM4om4y6n9vhxijocMw5LaeQPceGyNOEScWXXrNKAcUsK74klQmiPOoI2qI1zU5v2HrilKmkOELH81U8/Qmmjmg7ouOdOHqlZAxW9Sv8M=</P>
<Q>lzRdUtp56eZHIgxRemvdHciGIfc=</Q>
<G>Z/2T+jXvv0ZLswbuMd9DxrHldakJxZ8JNGRf1QzN09B2VO9WYAzUy0S+J8hbYQjP/jzWbmL5LaK57v+MUOmOHzFwNqfVMe9OUglUfF3nN990ur9hp6csu8+vCEQt3EoI8Wmh/b2yqhtKRN6U494vf33WKo1NCNQapB+iWVQ/egQ=</G>
<Y>ykcPXFIxWvYDDbbY05oD3hD6LsM5rk76FakUY8YiCo8ZwWbMIlQw+v5nOYS9vpQaZAzUqxx9OXIGSTUGItruTARkDqZ0nGKL0r94Zhog1Y0wU2AVKJh8Vjq/dLFyDDGZZsxBZtmI8TDyKGJbZqvzGbdGLhoRxRFmNi1fVsADv+U=</Y>
</DSAKeyValue>

<DSAKeyValue> <!-- this is paramteres of original DSACryptoServiceProvider-->
<P>2arEQPD3/tKm7pJF1y4gN0/4WzSGfkgFwdmtmoUf/gHoXpdBetRH/5j98qo4k1ybePxM4om4y6n9vhxijocMw5LaeQPceGyNOEScWXXrNKAcUsK74klQmiPOoI2qI1zU5v2HrilKmkOELH81U8/Qmmjmg7ouOdOHqlZAxW9Sv8M=</P>
<Q>lzRdUtp56eZHIgxRemvdHciGIfc=</Q>
<G>Z/2T+jXvv0ZLswbuMd9DxrHldakJxZ8JNGRf1QzN09B2VO9WYAzUy0S+J8hbYQjP/jzWbmL5LaK57v+MUOmOHzFwNqfVMe9OUglUfF3nN990ur9hp6csu8+vCEQt3EoI8Wmh/b2yqhtKRN6U494vf33WKo1NCNQapB+iWVQ/egQ=</G>
<Y>ykcPXFIxWvYDDbbY05oD3hD6LsM5rk76FakUY8YiCo8ZwWbMIlQw+v5nOYS9vpQaZAzUqxx9OXIGSTUGItruTARkDqZ0nGKL0r94Zhog1Y0wU2AVKJh8Vjq/dLFyDDGZZsxBZtmI8TDyKGJbZqvzGbdGLhoRxRFmNi1fVsADv+U=</Y>
<Seed>1hiZoCQFivF9xDZdQEGue65oObA=</Seed>
<PgenCounter>Og==</PgenCounter>
</DSAKeyValue>

You see that original DSACryptoServiceProvider includes Seed and PgenCounter, while after generating certificate with Bouncy Castle, certificate's public key does not contain them. Those parameters are optional (in a sense that public key may not contain then), but if they are present, they should be present on both sides (private and public). How can we workaround this? Here is the code:

using (DSACryptoServiceProvider csp = new DSACryptoServiceProvider(1024)) {
var parameters = csp.ExportParameters(true);
var keypair = DotNetUtilities.GetDsaKeyPair(parameters);
var gen = new X509V3CertificateGenerator();
var CN = new X509Name("CN=" + "TEST");
var SN = BigInteger.ProbablePrime(120, new Random());
gen.SetSerialNumber(SN);
gen.SetSubjectDN(CN);
gen.SetIssuerDN(CN);
gen.SetNotAfter(DateTime.Now.AddDays(1));
gen.SetNotBefore(DateTime.Now.Subtract(new TimeSpan(7, 0, 0, 0)));
gen.SetSignatureAlgorithm("sha1WithDSA");
gen.SetPublicKey(keypair.Public);
var newCert = gen.Generate(keypair.Private);

var certificateDSA = new X509Certificate2(DotNetUtilities.ToX509Certificate(newCert));
// added block
parameters.Seed = new byte[20];
unchecked {
parameters.Counter = (int) 0xFFFFFFFF;
}
csp.ImportParameters(parameters);
// end of added block
certificateDSA.PrivateKey = csp;
StringBuilder builder = new StringBuilder();

builder.AppendLine("-----BEGIN CERTIFICATE-----");
builder.AppendLine(Convert.ToBase64String(certificateDSA.Export(X509ContentType.Pkcs12), Base64FormattingOptions.InsertLineBreaks));
builder.AppendLine("-----END CERTIFICATE-----");

string result = builder.ToString();
}

What we do here is after generating everything, but before assigning private key to certificate, we "removing" seed and counter from DSACryptoServiceProvider parameters. This code does not throw errors and completes fine. Maybe there are some caveats in this workaround, but nevertheless it might be useful to futher investigating the issue, even if it not fixes it completely.

Add Private Key to X509 Certificate before export to p12 in c#

First I would like to thank bartonjs above for his help solving this. The different lengths got me thinking about this in a different way.

My edit above solved it. I assumed that by calling:

 pfx.SetKeyEntry("MyKey", key, chain);

it would set the key into the certificates in the chain. Instead I had to export them like so:

using (MemoryStream stream = new MemoryStream())
{
pfx.Save(stream, "Password".ToCharArray(), new SecureRandom());//The SecureRandom must be responsible for the different strings/lengths when re-generated.
using (BinaryReader reader = new BinaryReader(stream))
{
byte[] p12 = stream.ToArray();
//then I can save this off somewhere - in my case the db.
}
}

Now I have a p12 with a certificate and private key attached and it all works with Apple's push notification system.

Use .p12 to create CA?

A CA is a Certificate Authority. It is an organization that generates a public/private key pair for you. A PKCS#12 is a standard that explains how to store public/private key pairs in a file. Your .p12 file is such a file. It is a keystore that contains digital certificates (a public and a private key pair) as defined in PKCS#12.

If you have a .p12, there are two options:

  • Either your certificates are self-signed. In this case, you have created your own public and private key pair and you've stored it inside a .p12 file. As no CA is involved, there is no way to check if you created those certificates, or if somebody else impersonating you created those certificates.
  • Or you obtained that .p12 file from a CA. In this case, the CA signed your certificate and there is a certificate chain that eventually leads to the root certificate of your CA. A CA doesn't hand out such certificate to just anybody. A CA will first check that you are who you say you are. This way, whichever party gets confronted with your certificate (for instance because you used your private key to sign your code), knows that you are who you say you are, provided that your CA is a trusted party (there are CAs and then there are CAs).

This image shows how it works:

Sample Image

You are Bob, Apple is Alice, Trent is your CA. Apple wants you to work with a CA because Apple doesn't know you, but Apple knows CA. Once you make yourself known to the CA and once the CA trusts you, Apple will trust you.

See https://en.wikipedia.org/wiki/Alice_and_Bob to find out how the names in this image were chosen.

So the real question is: can you extract your public key from your .p12 file and show it to us so that we can see if your certificate is self-signed or if it's signed by a CA? Without that info, I don't think anyone can help you.

Swift iOS Client Certificate Authentication

Technically, when someone I know needed the implementation in Swift, he used the following Objective-C implementation in order to get the NSURLCredential object to the connection; based on the private key and X509 Certificate pair contained in a PKCS12 keystore.

Sorry, I don't have access to the source with the Swift solution. All I know is that the NSURLCredential was returned to Swift, and used directly in the http url connection there. It's similar to this one, though.

I'm not an iOS dev so I won't be able to help you out with the "bridging to Swift" part.

- (void)getMessageWithURL:(NSString *)url {

NSURL *URL = [NSURL URLWithString:url];

NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setURL:URL];
[request setHTTPMethod:@"GET"];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[connection self];
}

- (void)postMessageWithURL:(NSString *)url withContent:(NSString *)content {

NSData *postData = [content dataUsingEncoding:NSUTF8StringEncoding];
NSString *postLength = [NSString stringWithFormat:@"%d", [postData length]];

NSURL *myURL = [NSURL URLWithString:url];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:myURL cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:60];

[request setHTTPMethod:@"POST"];
[request setValue:postLength forHTTPHeaderField:@"Content-Length"];
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
[request setHTTPBody:postData];

NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[connection self];

}

- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace {
return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
}

- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
NSLog(@"didReceiveAuthenticationChallenge");
}

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
responseData = [[NSMutableData alloc] init];
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[responseData appendData:data];
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
NSLog(@"Unable to fetch data");
NSLog(@"%@", error);
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSLog(@"Succeeded! Received %lu bytes of data", (unsigned long)[responseData
length]);

NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
NSLog(@"%@", responseString);

[bridge callHandler:handlerName data:responseString];

}

- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {

/*
Reading the certificate and creating the identity
*/
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = paths[0]; // Get documents directory

NSData *p12data = [CertificateManager getP12Data]; //returns essentially a byte array containing a valid PKCS12 certificate

if (!p12data) {
return;
NSAssert(p12data, @"Couldn't load p12 file...");
}

CFStringRef password = CFSTR("password");

const void *keys[] = {kSecImportExportPassphrase};
const void *values[] = {password};
CFDictionaryRef optionsDictionary = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
CFArrayRef p12Items;

OSStatus result = SecPKCS12Import((__bridge CFDataRef) p12data, optionsDictionary, &p12Items);

if (result == noErr) {
CFDictionaryRef identityDict = CFArrayGetValueAtIndex(p12Items, 0);
SecIdentityRef identityApp = (SecIdentityRef) CFDictionaryGetValue(identityDict, kSecImportItemIdentity);

SecCertificateRef certRef;
SecIdentityCopyCertificate(identityApp, &certRef);

SecCertificateRef certArray[1] = {certRef};
CFArrayRef myCerts = CFArrayCreate(NULL, (void *) certArray, 1, NULL);
CFRelease(certRef);

NSURLCredential *credential = [NSURLCredential credentialWithIdentity:identityApp certificates:nil persistence:NSURLCredentialPersistenceNone];
CFRelease(myCerts);

[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
}
else {
// Certificate is invalid or password is invalid given the certificate
NSLog(@"Invalid certificate or password");
NSError *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:result userInfo:nil];
return;
}
}

EDIT: Har har, very funny, downvoting me twice when you yourself didn't bother while the bounty was up. *grumble *

Anyways, to use the following above, you just need to access it from Swift.

func connection(connection: NSURLConnection, willSendRequestForAuthenticationChallenge challenge: NSURLAuthenticationChallenge) {
if let p12Data = UserManager.currentP12,
let credential = CertificateManager.getCredentialsForP12(p12Data) as? NSURLCredential {
challenge.sender.useCredential(credential, forAuthenticationChallenge: challenge)
} else {
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
}
}

That uses this.

+ (id)getCredentialsForP12:(NSData *)p12 {
NSData* p12data = p12;
const void *keys[] = {kSecImportExportPassphrase};
const void *values[] = {CFSTR("thePassword")};
CFDictionaryRef optionsDictionary = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
CFArrayRef p12Items;
OSStatus result = SecPKCS12Import((__bridge CFDataRef) p12data, optionsDictionary, &p12Items);
if (result == noErr) {
CFDictionaryRef identityDict = CFArrayGetValueAtIndex(p12Items, 0);
SecIdentityRef identityApp = (SecIdentityRef) CFDictionaryGetValue(identityDict, kSecImportItemIdentity);
SecCertificateRef certRef;
SecIdentityCopyCertificate(identityApp, &certRef);
SecCertificateRef certArray[1] = {certRef};
CFArrayRef myCerts = CFArrayCreate(NULL, (void *) certArray, 1, NULL);
CFRelease(certRef);

NSURLCredential *credential = [NSURLCredential credentialWithIdentity:identityApp certificates:nil persistence:NSURLCredentialPersistenceNone];
CFRelease(myCerts);
return credential;

}
else {
// Certificate is invalid or password is invalid given the certificate
NSLog(@"Invalid certificate or password");

UIAlertView* av = [[UIAlertView alloc] initWithTitle:@"Error" message:@"Invalid cert or pass" delegate:nil cancelButtonTitle:@"ok" otherButtonTitles: nil];
[av show];
NSError *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:result userInfo:nil];
return nil;
}

EDIT: A swift version of the above is here, although it was messy enough that we rather just didn't use it.

            var p12items : Unmanaged<CFArrayRef>?

let index: CFIndex = 1
let password: CFString = "password"
let key = kSecImportExportPassphrase.takeRetainedValue() as String
var values = [unsafeAddressOf(password)]
var keys = [unsafeAddressOf(key)]

var keyCallbacks = kCFTypeDictionaryKeyCallBacks
var valueCallbacks = kCFTypeDictionaryValueCallBacks

let length: CFIndex = p12Data.length
let p12CfData: CFData = CFDataCreate(kCFAllocatorDefault, UnsafePointer<UInt8>(p12Data.bytes), length)

let options = CFDictionaryCreate(kCFAllocatorDefault, &keys, &values, index, &keyCallbacks, &valueCallbacks)
let result = SecPKCS12Import(p12CfData, options, &p12items)

if result == noErr {

let idIndex: CFIndex = 0
var items = p12items?.takeRetainedValue()
var identityDict = CFArrayGetValueAtIndex(items!, idIndex)

var key = kSecImportItemIdentity.takeRetainedValue() as String
var keyAddress = unsafeAddressOf(key)
var identityApp: SecIdentityRef = CFDictionaryGetValue(identityDict, keyAddress)
var certRef : Unmanaged<SecCertificateRef>?
SecIdentityCopyCertificate(identityApp, &certRef)

var cert: SecCertificateRef = certRef!.takeRetainedValue()
var certArray = [unsafeAddressOf(cert)]
var arrayCallback = kCFTypeArrayCallBacks
var myCerts: CFArrayRef = CFArrayCreate(kCFAllocatorDefault, &certArray, index, &arrayCallback);

let credential: NSURLCredential = NSURLCredential(identity: identityApp, certificates: [AnyObject](), persistence: NSURLCredentialPersistence.None)


Related Topics



Leave a reply



Submit