-
-
Notifications
You must be signed in to change notification settings - Fork 112
Generating Certificates
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!
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.
To generate successfully a Pass, we'll need to obtain three elements:
- Signer Certificate (Developer)
- Signer Certificate Key (Developer)
- WWDR (Apple WorldWide Developer Relations) G4 Certificate (visit Apple PKI Portal to download it)
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.
-
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 thepass.json
file or as a prop. -
Confirm and register the new identifier.
-
In the list of your passTypeIds (filter), click on your new pass id to edit it.
-
Click on "Create Certificate". From now on, the instructions will split and you'll have to follow your way between the two below
-
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 likepass.cer
. -
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 selectExport "\<certname\>"
. Choose a password (and write it down) and you will get a PKCS#12 file (.p12
). -
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>
-
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? 🌈)
-
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
-
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 yoursignerKey
password here, if you have one, through the fieldinput_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
-
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 likepass.cer
-
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 assignerCert
.# .cer to .pem $ openssl x509 -inform DER -outform PEM -in <cert name>.cer -out signerCert.pem
-
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 keywwdr
. 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!
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:
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).
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.
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
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
.
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.
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.
Of course, not! Passkit-generator is just a library to aims to make it easier to generate them.
The generic flow is the following:
-
Fill all the data in
pass.json
, along with your signing information (likepassTypeIdentifier
andteamIdentifier
) -
Add all the needed assets for your pass type in your model
-
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 (likeen.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
-
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>
-
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
).