Skip to content

Instantly share code, notes, and snippets.

@chronos-tachyon
Created March 23, 2022 04:03
Show Gist options
  • Save chronos-tachyon/2a3e37b97e20f719c2f3a4766e09f88d to your computer and use it in GitHub Desktop.
Save chronos-tachyon/2a3e37b97e20f719c2f3a4766e09f88d to your computer and use it in GitHub Desktop.
A tool for splitting a key+cert+chain X.509 PEM file into separate key, cert, and chain PEM files.
#!/usr/bin/env python3
#
# Written by Donald King <chronos@chronos-tachyon.net>
# Public Domain.
#
# ==== CC0 https://creativecommons.org/publicdomain/zero/1.0/ ====
# [To the extent possible under law, I have waived all copyright ]
# [and related or neighboring rights to this script. This work is]
# [published from the United States of America. ]
import argparse
import os
import re
RE_BEGIN = re.compile(r'^-----BEGIN ([0-9A-Z]+(?: [0-9A-Z]+)*)-----$')
parser = argparse.ArgumentParser()
parser.add_argument('-i', '--input', help='path to input PEM file', required=True)
parser.add_argument('-k', '--key', help='path to output private key PEM file', required=True)
parser.add_argument('-c', '--cert', help='path to output certificate or cert + CA chain PEM file', required=True)
parser.add_argument('-C', '--ca', help='path to output CA chain PEM file')
parser.add_argument('--crlf', help='output PEM files in CRLF format, instead of LF')
parser.add_argument('--reverse', action='store_true', help='output the CA chain in reverse order (root first, then intermediate)')
args = parser.parse_args()
private_key = None
leaf_certificate = None
chain_certificates = []
eol = '\r\n' if args.crlf else '\n'
mode = 0
tag = None
partial = None
end = None
with open(args.input, 'r') as fp:
for line in fp:
line = line.rstrip('\r\n')
if mode == 0 and not line:
pass
elif mode == 0:
m = RE_BEGIN.match(line)
if not m:
raise ValueError('expected /-----BEGIN .*-----/, got {!r}'.format(line))
tag = m.group(1)
if tag.endswith(' PRIVATE KEY'):
mode = 1
elif tag == 'CERTIFICATE' and leaf_certificate is None:
mode = 2
elif tag == 'CERTIFICATE':
mode = 3
else:
raise ValueError('expected BEGIN FOO PRIVATE KEY or BEGIN CERTIFICATE, got {!r}'.format('BEGIN ' + tag))
partial = line + eol
end = '-----END ' + tag + '-----'
elif line != end:
partial += line + eol
elif mode == 1:
partial += line + eol
private_key = partial
mode = 0
tag = None
partial = None
end = None
elif mode == 2:
partial += line + eol
leaf_certificate = partial
mode = 0
tag = None
partial = None
end = None
elif mode == 3:
partial += line + eol
chain_certificates.append(partial)
mode = 0
tag = None
partial = None
end = None
if args.reverse:
chain_certificates.reverse()
if args.ca:
with open(args.ca, 'x') as fp:
for chain_certificate in chain_certificates:
fp.write(chain_certificate)
with open(args.cert, 'x') as fp:
fp.write(leaf_certificate)
if not args.ca:
for chain_certificate in chain_certificates:
fp.write(chain_certificate)
os.umask(0o077)
with open(args.key, 'x') as fp:
fp.write(private_key)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment