How to create a PKCS12 compatible with iOS's Multipeer Connectivity with node-forge ?

I am trying to achieve **Multipeer Connectivity** communications security through clients authentication using **X509 certificate**.



To do so, I am generating the clients' certificates in my server using **node-forge**. First, the **X509** is created then it is transformed into a **PKCS12 base64 string** that is returned to the client.



That is basically the code I am using :

  var username = "client1"
  
    // Create key pair
  var pki = forge.pki;
  var keys = pki.rsa.generateKeyPair(2048);
  var cert = pki.createCertificate();
    // Creating the certificate
  cert.publicKey = keys.publicKey;
  cert.serialNumber = '01'; // TODO : generate random number and have a little custom algo to verify it !!
  cert.validity.notBefore = new Date();
  cert.validity.notAfter = new Date();
  cert.validity.notAfter.setTime(cert.validity.notBefore.getTime() + msWeek);
  var subject = [{
       name : "commonName",
       value : username
  }, {
       name : "organizationName",
       value : "My Company"
  }, {
       name : "organizationalUnitName",
  value : "MU"
  }, {
       name : "stateOrProvinceName",
       value : "Ile-de-France"
  }, {
       name : "countryName",
       value : "FR"
  }, {
       name : "localityName",
       value : "Paris"
  }, {
       name : "emailAddress",
       value : "hello@world.com"
  } ];
  var issuer = [{
       name : "commonName",
       value : "MPC App"
  }, {
       name : "organizationName",
       value : "My Company"
  }, {
       name : "organizationalUnitName",
       value : "MU"
  }, {
       name : "stateOrProvinceName",
       value : "Ile-de-France"
  }, {
       name : "countryName",
       value : "FR"
  }, {
       name : "localityName",
       value : "Paris"
  }, {
       name : "emailAddress",
       value : "hello@world.com"
  } ];
  cert.setSubject(subject);
  cert.setIssuer(issuer);
  
    // Extensions
  cert.setExtensions([{
       name: 'basicConstraints',
       cA : true
  } , {
       name : 'keyUsage',
       digitalSignature : true,
       keyCertSign : true,
       nonRepudiation : true,
       keyEncipherment : true,
       dataEncipherment : true
  }, {
       name : 'extKeyUsage',
       clientAuth : true,
       serverAuth : false,
       codeSigning : true,
       emailProtection : false,
       timeStamping : true
  }, {
       name : 'nsCertType',
       client : true,
       server : false,
       email : false,
       objsign : true,
       sslCA : false,
      emailCA : false,
       objCA : false
  }]);
  cert.sign(keys.privateKey);
  var asn1Cert = pki.certificateToAsn1(cert);
    // Create PKCS#12 from the certificate and encode to base64 string
  var p12Asn1 = forge.pkcs12.toPkcs12Asn1(keys.privateKey , cert, "iPhone");
  var p12Der = forge.asn1.toDer(p12Asn1).getBytes();
  return forge.util.encode64(p12Der);



However, when I import it in my iOS application, runtime keeps crashing telling me it has failed to read the contents of the PKCS#12 (different error from the bad password error though) by returning **errSecDecode**.



I don't know which part of my code is causing this error even though I suspect the **extensions** to be at the origin of these issues, by the way I don't really know what suits best to my usecase (two clients authenticating themselves for each other in order to communicate with **MultiPeer Connectivity**).



I would also like to know if I am doing something wrong when I encode my **PKCS#12** to a **base64 string** ?



If it helps, here is the code I'm using to import the **PKCS#12** in the **iOS** side after recovering the base64 string from the server.



    private func generateIdentity (base64p12 : String, password : String?) {
        print("gen id")
        let p12KeyFileContent = NSData(base64EncodedString: base64p12, options: NSDataBase64DecodingOptions.IgnoreUnknownCharacters)
      
        if (p12KeyFileContent == nil) {
            NSLog("Cannot read PKCS12 data")
            return
        }
      
        let options = [String(kSecImportExportPassphrase):password ?? ""]
        var citems: CFArray? = nil
        let resultPKCS12Import = withUnsafeMutablePointer(&citems) { citemsPtr in
            SecPKCS12Import(p12KeyFileContent!, options, citemsPtr)
        }
        if (resultPKCS12Import != errSecSuccess) {
            print("resultPKCS12Import :", resultPKCS12Import)
            return
        }
      
        let items = citems! as NSArray
        let myIdentityAndTrust = items.objectAtIndex(0) as! NSDictionary
        let identityKey = String(kSecImportItemIdentity)
      
        identity = myIdentityAndTrust[identityKey] as! SecIdentityRef
        hasCertificate = true
        print("cert cre", identity)
    }


Thank you in advance

I would also like to know if I am doing something wrong when I encode my PKCS#12 to a base64 string ?

Your concern here is valid.

SecPKCS12Import
definitely wants the DER-encoded PKCS#12, so you need to strip the Base64 encoding before you pass it in to that routine.

First things first, try importing the PKCS#12 into the keychain on the Mac. If that fails, there’s something wonky with the PKCS#12 itself. OTOH, if that works and iOS fails to import the same PKCS#12, we need to look at iOS-specific causes (your code, or maybe an iOS-specific PKCS#12 restriction).

IMPORTANT To do the above, decode the Base64 data back to DER and then give the file the

.p12
extension.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Maybe I can skip the part (in my JavaScript code) where I encode to a base64 string and instead return a sequence of bytes.

Wouldn't it work if I just take the DER bytes output of my server to build a NSData of the certificate like so :

let p12KeyFileContents = NSData(contentsOfURL : url)


If not, what's the easiest way to store this file on the iPhone ?


I tried to open the raw bytes PKCS#12 created but it throws a security error with Keychain Access. Are the certificates required to be created on a Apple device for them to be opened in Keychain Access and/or with the Security Framework ?


edit : When trying to decode the raw bytes certificate output of my server with openssl :


openssl pkcs12 -in one.p12 -passin pass:"iPhone" -out one.pem


I get the following output :

3074332872:error:0D07207B:asn1 encoding routines:ASN1_get_object:header too long:asn1_lib.c:150:


Thus, I think node-forge is not doing its job properly. But I still don't know why.

I have found the source of the issue.

iOS apparently requires a TripleDES encryption algorithm. So I was able to fix my code by doing


var p12Asn1 = forge.pkcs12.toPkcs12Asn1(asn1Cert.privateKey , [asn1Cert.rawCert], "iPhone", {algorithm : '3des'});


Anyway, thank you for your valuable help 🙂

How to create a PKCS12 compatible with iOS's Multipeer Connectivity with node-forge ?
 
 
Q