Skip to content

Generating Certificates

Alexander Cerutti edited this page Aug 9, 2023 · 25 revisions

So, here I'll try to concentrate all the knowledge about generating certificates and signature for a correct Pass generation, that have been accumulated in discussions, issues, and from several people using passkit-generator!

Prerequisites

To achieve our goal, we'll need to install OpenSSL/LibreSSL in our CLI. We'll be using it partially or entirely, based on the flow you'll choose later in this document. Install it before going on.

Please note that using OpenSSL under Windows might require you to create a configuration file.

You can load it by adding -config <path/to/.cnf> to the OpenSSL commands you want to execute.

Optional: create a folder cert to contain all the data we are going to generate/download.

Completely understanding the goal

To generate successfully a Pass, we'll need to obtain three elements:

If you are using G1, please note that it expired on 07 February 2023 and you need to download and execute the steps again for WWDR G4.

Ready? Set? Steps!

  1. Create a new pass type identifier (direct link) and provide it with a description and a reverse-domain identifier (starting with "pass."). You will have to put this identifier as a value for passTypeIdentifier in the pass.json file or as a prop.

  2. Confirm and register the new identifier.

  3. In the list of your passTypeIds (filter), click on your new pass id to edit it.

  4. Click on "Create Certificate". From now on, the instructions will split and you'll have to follow your way between the two below


Generate certificates through MacOS


  1. You can now follow the instructions, like providing the Certificate Signing Request required. To generate a Certificate Signing Request (CSR) through the Keychain Access.app, you can follow this guide created by Apple about the topic. Once done, you'll be able to download a certificate like pass.cer.

  2. Once downloaded the certificate in .cer format, open (import) the downloaded certificate in macOS Keychain Access. Filter for "Certificates" and identify your imported certificate. Right-click on it and select Export "\<certname\>". Choose a password (and write it down) and you will get a PKCS#12 file (.p12).

  3. Open the terminal, place yourself where you want to save the files, and use the following OpenSSL commands, by changing the contents between angular brackets first. You'll have to choose a secret passphrase (and write it down) that you'll use also in the application. The first command assumes you haven't created the folder yet. You might want to add the -legacy flag at the end of the OpenSSL commands. Refer to the paragraph OpenSSL change of P12 encryption algorithm for more details.

    # Creating and changing dir
    $ mkdir "certs" && cd $_
    
    # Extracting key and cert from pkcs12
    # <your-password> for -passin is the pass for the P12
    $ openssl pkcs12 -in <cert-name>.p12 -clcerts -nokeys -out signerCert.pem -passin pass:<your-password>
    
    # <your-password> for -passin is the pass for the P12. <secret-passphrase> is the pass you'll pass to passkit-generator to decrypt privateKey.
    $ openssl pkcs12 -in <cert-name>.p12 -nocerts -out signerKey.pem -passin pass:<your-password> -passout pass:<secret-passphrase>
  4. Take the WWDR Certificate you downloaded earlier and repeat step 6. Instead of exporting it as .p12 (you should also be unable to export it as such), export it as .pem and save it somewhere great (like... Over The Rainbow? 🌈)


Generate certificates through Terminal


  1. To provide Apple with the required Certificate Signing Request, we'll have first to generate a private key. This will be your signerKey. Having it as .key or .pem doesn't make any difference: it is still a PEM.

    # If you want to set it as a passphrase, add "-passout pass:<your-passphrase>" before "2048".
    # If you follow the passphrase way, you'll have to set a "-passin pass:<your-passphrase>" (or insert it manually)
    # in the next commands and, once done, set it to passkit-generator at `signerKeyPassphrase`.
    
    $ openssl genrsa -out <your-key-name>.key 2048
  2. Generate a CSR using your private key. Usually, the final file should have a .csr extension, but there is no difference: .csr is an ASN.1 Base64 encoded text. Therefore it can have any extension you want. You'll be prompted to insert a few pieces of information one per prompt. Continue reading.

    $ openssl req -new -key <your-key-name>.key -out request.certSigningRequest

    In the required information, you'll have to insert Apple CA's information, like below (those among **asterisks**). If none, press Enter to skip. After the email address, you won't need any further information. So press Enter until you won't finish.

     Country Name (2-letter code) [AU]: **US**
     State or Province Name [Some-State]: **United States**
     Locality Name []:
     Organization Name [Internet Widgits Pty Ltd]: **Apple Inc.**
     Organizational Unit Name []: **Apple Worldwide Developer Relations**
     Common Name []: **Apple Worldwide Developer Relations Certification Authority**
     Email Address []: **your-email**
    

    If you are curious about how a CSR is composed, use this command:

    # Optional, just for curious people, like George, even if it's a monkey.
    $ openssl asn1parse -i -in request.certSigningRequest

    You can also provide this information without manually prompting them, by providing an OpenSSL configuration file through -config file.cnf. Here below an example. You can also set your signerKey password here, if you have one, through the field input_password.

    [req]
    input_password      = 12346
    prompt = no
    distinguished_name  = cert_req
    
    [cert_req]
    commonName             = Apple Worldwide Developer Relations Certification Authority
    countryName            = US
    stateOrProvinceName    = United States
    localityName           = EMPTY
    organizationName       = Apple Inc
    organizationalUnitName = IT
    emailAddress           = whocares@ido.not
    
  3. Take the generated file request.certSigningRequest and upload it to the provisioning portal at step 4. Once processed, you'll be able to download a certificate like pass.cer

  4. Once downloaded the certificate, you'll have to convert it to .pem (from a DER-encoded to PEM Base64 encoded). Save the result somewhere safe. You'll be using it in your application as signerCert.

    # .cer to .pem
    $ openssl x509 -inform DER -outform PEM -in <cert name>.cer -out signerCert.pem
  5. Take the WWDR Certificate you downloaded earlier and convert it to a .pem base64 by repeating step 8. You'll be using it in your application for the key wwdr. Save the result somewhere safe (like... Over the rainbow? 🌈)

    # .cer to .pem
    $ openssl x509 -inform DER -outform PEM -in <cert name>.cer -out wwdr.pem

And you are done. 🎉 Now get back and try to create your first pass!


OpenSSL change of P12 encryption algorithm

When using OpenSSL >v3.0.0, you might encounter an error while extracting the certificate and the signature from the PKCS#12 issued by Keychain Access:

Error outputting keys and certificates
005E04F401000000:error:0308010C:digital envelope routines:inner_evp_generic_fetch:unsupported:crypto/evp/evp_fetch.c:341:Global default library context, Algorithm (RC2-40-CBC : 0), Properties ()

This is due to an OpenSSL default algorithm change, started from v3.0.0 (Sept 2021). Starting from this version, it changed from RC2-40-CBC (possibly along with others) to AES-256-CBC.

To verify your OpenSSL/LibreSSL version, you can run this command:

$ openssl version -a

It should return something like one of these two:

immagine immagine

At the moment of writing, MacOS comes out with LibreSSL (an OpenSSL fork), which seems to not be affected by this change. This could be the reason why Keychain Access, on MacOS Ventura, still issues p12 with RC2-40-CBC.

As we don't know if this behavior will ever change in the future, especially on LibreSSL, here's how you can recognize if your .p12 uses the old (legacy) or the new algorithm.

Run this command, by replacing the passphrase placeholder with the password you chose when exporting the P12.

$ openssl pkcs12 -in certificates.p12 -nocerts -nokeys -passin pass:<signerKey passphrase> -info -legacy

Here's a comparison between a .p12 with the new algorithm (above) and a .p12 with the old algorithm (below).

immagine

As you might have noticed, the way to read the .p12 file encrypted with the old algorithm is to use the flag -legacy, which tells OpenSSL to use the legacy provider.

Understanding more about certificates

Invalid data error reading pass. The passTypeIdentifier or teamIdentifier provided may not match your certificate, or the certificate trust chain could not be verified

There might be several things going on here: your certificates are wrong. There are two things you can look at to understand where is the issue. You can use the following two commands to open a detailed description of your certificates:

You can analyze the certificates by running this command. We'll be using them below.

# for PEM certificates
$ openssl x509 -inform PEM -in <certificate>.pem -noout -text

# for CER certificates
$ openssl x509 -inform DER -in <certificate>.cer -noout -text

Which passTypeIdentifier and teamIdentifier am I using?

When handling several certificates (like I do when testing), you might incur into loading the wrong certificates into passkit-generator or, at least, have mismatching passTypeIdentifier and teamIdentifier between pass.json and the certificates.

Use the commands above to open your signerCert file. Look at the "Subject" line. Then compare Pass Type ID with passTypeIdentifier and OU (Organization Unit) with teamIdentifier.

immagine

WWDR Certificate

As said before, Apple Wallet accepts only WWDR Certificate G4. Using G2, G3, G5, and G6 will lead the validation to fail with the error above.

Assuming that passTypeIdentifier and teamIdentifier match correctly (as explained before), we can proceed to analyze the concept of certificate trust chain.

When Apple issues the certificate (signerCert) following the upload of a Certificate Signing Request, the Certificate Authority (CA) that issues the certificate, has a specific key identifier.

So, we can compare our signerCert, G1, G3, G4, and G5. As you can see in the extraction of data here below, only G1 matches the Subject Key Identifier (so, the identifier of what is being protected) with our certificate's Authority Key. So it might be possible that a check is performed by Apple Wallet when a pass is imported.

Please note that there's nothing that can confirm this: this is a work made upon observation and no Apple Documentation is available about this. Also, this reasoning does not explain how a signerCert issued with G4, can match a WWDR G1. Some details might be missing.

Even if this screenshot has been done with G1, the reasoning is still valid for G4

By running these two commands on G2 and G6, you'll also be able to see that those use an ECDSA Algorithm with, respectively, SHA-256 and SHA-384, so they are not compatible at all with Wallet.

Since the supported WWDR certificate is only one, why not hardcoding it inside passkit-generator?

This paragraph was valid when G1 was the only certificate available. It became invalid starting from 27 January 2022, when G4 was released and started working along with G1 (up to Feb 2023).

Since v2, passkit-generator is attempted to be kept out of out-of-scope things, like file downloading for example. Certificates handling (except for reading) is definitely out of scope. Also, hardcoding the WWDR certificate would mean that each WWDR certificate expiration or invalidation would correspond to a forced release of a major version of passkit-generator, even without an API change. The best case is the natural expiration, which should happen on 07 Feb 2023 - then nothing else because each WWDR Certificate should last about 10 years.

Try to imagine, one day (like in a year or in 10 years), in which I won't maintain this package actively anymore: an expired certificate might mean that this package might become wasted and all the knowledge in the wiki with it, due to forks. I definitely don't want this all to get wasted. I hope you'll understand my point of view.

Do I really need passkit-generator to create Apple Wallet passes?

Of course, not! Passkit-generator is just a library to aims to make it easier to generate them.

The generic flow is the following:

  1. Fill all the data in pass.json, along with your signing information (like passTypeIdentifier and teamIdentifier)

  2. Add all the needed assets for your pass type in your model

  3. Generate the SHA-1 string for each of your files and add them in a new file manifest.json, in an object with the file's path (like en.lproj/thumbnail.png) as key and SHA-1 as value. This can be done through the following command:

    $ openssl sha1 pass.json
    # Output like: SHA1(pass.json)= 827ddc06b176aa61f22e8773b7360854e3758b8a
    # You care of just the second part

    To make it easy, I've created a bash script that automatically creates manifest.json file with a valid JSON in it, by finding all the files (also in all subdirectories), calculating the SHA-1 output like above, extracting the file path and the SHA-1, and adding the final comma at the end of each line (and removing just the last one).

    $ echo "{$(RESULT=""; for i in $(find * -type f); do RESULT+=$(openssl sha1 -binary $i | xxd -p | awk -v filename="$i" '{print "\"" filename "\":" "\"" $1 "\"," }'); done; echo $RESULT | sed -nE 's/(.*),/\1/p')}" >> manifest.json
  4. Generate a signature to zip them all, by using manifest.json and your certificates, along with the signerKey passphrase (if needed). signature file must be named as-is.

    $ openssl smime -binary -sign -certfile WWDR.pem -signer signerCert.pem -inkey signerKey.pem -in manifest.json -out signature -outform DER -passin pass:<signerKey passphrase>
  5. Take all your files, along with signature, and zip them all. Rename the .zip file to .pkpass. Serve cold with the correct mimeType (application/vnd-apple.pkpass).