35

Note : This is not really a question because I already found the answer but since I didn't find it easily here I will post it so that it can benefit others.

Question : How to read a concatenated PEM file as the one used by apache/mod_ssl directive SSLCACertificateFile ?

Answer (original) (source) :

cat $file|awk 'split_after==1{n++;split_after=0} /-----END CERTIFICATE-----/ {split_after=1} {print > "cert" n ".pem"}'

This can leave an empty file if there's a blank line at the end, such as with openssl pkcs7 -outform PEM -in my-chain-file -print_certs. To prevent that, check the length of the line before printing:

cat $file|awk 'split_after==1{n++;split_after=0}
   /-----END CERTIFICATE-----/ {split_after=1}
   {if(length($0) > 0) print > "cert" n ".pem"}' 

Answer 29/03/2016 :

Following @slugchewer answer, csplit might be a clearer option with :

csplit -f cert- $file '/-----BEGIN CERTIFICATE-----/' '{*}'
  • This may be a dumb question, but why would I need to split my pem file? – Ashwani Agarwal Mar 26 '16 at 8:06
  • 5
    @AshwaniAgarwal You want to split a PEM file when it contains several certificates and you wish to examine the certificates individually with tools such as openssl that take one certificate to analyze. – Law29 Mar 26 '16 at 9:13
  • Additionally, some tools or servers want a combined file with cert and key, while others want them separate. – captncraig Jan 6 '17 at 17:55
  • I had to add '%-----BEGIN CERTIFICATE-----%' to the csplit command line to prevent an empty file. Seems to match what the man page specifies: csplit -f ./tmp/cert- $file '%-----BEGIN CERTIFICATE-----%' '/-----BEGIN CERTIFICATE-----/' '{*}' – Craig Hicks Sep 20 '17 at 6:17
  • 2
    use "csplit -z" to not leave empty files. – Paul M Oct 20 '17 at 10:53
23

The awk snippet works for extracting the different parts, but you still need to know which section is the key / cert / chain. I needed to extract a specific section, and found this on the OpenSSL mailinglist: http://openssl.6102.n7.nabble.com/Convert-pem-to-crt-and-key-files-tp47681p47697.html

# Extract key
openssl pkey -in foo.pem -out foo-key.pem

# Extract all the certs
openssl crl2pkcs7 -nocrl -certfile foo.pem |
  openssl pkcs7 -print_certs -out foo-certs.pem

# Extract the textually first cert as DER
openssl x509 -in foo.pem -outform DER -out first-cert.der
  • nice command set :) I'll keep it for future use, but in my use case above, I'm working with a cert-only file containing 50+ CA certs ==> no pkey nor chain – Cerber Mar 20 '15 at 12:42
  • 2
    I think this is superior to awk solution, let the openssl do the parsing + you get the conversion. – Rusty Mar 24 '16 at 16:22
  • I'm sorry but only pkey command is correct. Second and third don't do what you advertise - they do something else. In some cases the result is good in some cases it can generate mysterious behaviors in consumers. Edited a bit. – kubanczyk Jun 22 '17 at 4:46
15

This was previously answered on StackOverflow :

awk '
  split_after == 1 {n++;split_after=0}
  /-----END CERTIFICATE-----/ {split_after=1}
  {print > "cert" n ".pem"}' < $file

Edit 29/03/2016 : See @slugchewer answer

14

The split command is available on most systems, and its invocation is likely easier to remember.

If you have a file collection.pem that you want to split into individual-* files, use:

split -p "-----BEGIN CERTIFICATE-----" collection.pem individual-

If you don't have split, you could try csplit:

csplit -f individual- collection.pem '/-----BEGIN CERTIFICATE-----/' '{*}'
  • 2
    Sorry, none of my systems (busybox, fedora, centos) show a -p option (nor the manpages I read) on split. Maybe you're using a special binary / package – Cerber Mar 25 '16 at 10:25
  • 1
    @Cerber Could try csplit instead... (see edit above) – squidpickles Mar 26 '16 at 5:33
  • 1
    works fine with csplit ! – Cerber Mar 29 '16 at 8:52
  • On FreeBSD I get from csplit: csplit: *}: bad repetition count (but split seems to work) – Gwyneth Llewelyn Aug 10 '17 at 22:11
3

If you want to get a single certificate out of a multi-certificate PEM bundle, try:

$ openssl crl2pkcs7 -nocrl -certfile INPUT.PEM | \
    openssl pkcs7 -print_certs | \
    awk '/subject.*CN=host.domain.com/,/END CERTIFICATE/'
  • The first two openssl commands will process a PEM file and and spit it back out with pre-pended "subject:" and "issuer:" lines before each cert. If your PEM is already formatted this way, all you need is the final awk command.
  • The awk command will spit out the individual PEM matching the CN (common name) string.

source1 , source2

  • I don't see this in your source. Beside, PEM are Base64 encoded you won't find text like "subject", "CN", ... with awk – Cerber May 12 '16 at 9:29
  • 1
    Yes, this doesn't work for every type of PEM. If you extract a P7B to PEM using openssl, it will have a subject line listed before each certificate. Or you can modify to any string you segment your PEM file with. – cmcginty May 12 '16 at 9:54
  • Updated answer to handle when PEM does not contain "subject" – cmcginty May 13 '16 at 1:22
2

Also worth noting that PEM files are just a collection of keys/certificates inside BEGIN/END blocks, so it's pretty easy to just cut/paste if it's just a single file with one or two interesting entities...

1

If you are handling full chain certificates (i.e. the ones generated by letsencrypt / certbot etc), which are a concatenation of the certificate and the certificate authority chain, you can use bash string manipulation.

For example:

# content of /path/to/fullchain.pem
-----BEGIN CERTIFICATE-----
some long base64 string containing
the certificate
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
another base64 string
containing the first certificate
in the authority chain
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
another base64 string
containing the second certificate
in the authority chain
(there might be more...)
-----END CERTIFICATE-----

To extract the certificate and the certificate authority chain into variables:

# load the certificate into a variable
FULLCHAIN=$(</path/to/fullchain.pem)
CERTIFICATE="${FULLCHAIN%%-----END CERTIFICATE-----*}-----END CERTIFICATE-----"
CHAIN=$(echo -e "${FULLCHAIN#*-----END CERTIFICATE-----}" | sed '/./,$!d')

Explanation:

Instead of using awk or openssl (which are powerful tools but not always available, i.e. in Docker Alpine images), you can use bash string manipulation.

"${FULLCHAIN%%-----END CERTIFICATE-----*}-----END CERTIFICATE-----": from end of the content of FULLCHAIN, return the longest substring match, then concat -----END CERTIFICATE----- as it get stripped away. The * matches all the characters after -----END CERTIFICATE-----.

$(echo -e "${FULLCHAIN#*-----END CERTIFICATE-----}" | sed '/./,$!d'): from the beginning of the content of FULLCHAIN, return the shortest substring match, then strip leading new lines. Likewise, the * matches all the characters before -----END CERTIFICATE-----.

For a quick reference (while you can find more about string manipulation in bash here):

${VAR#substring}= the shortest substring from the beginning of the content of VAR

${VAR%substring}= the shortest substring from the end of the content of VAR

${VAR##substring}= the longest substring from the beginning of the content of VAR

${VAR%%substring}= the longest substring from the end of the content of VAR

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.