💾 Archived View for thrig.me › tech › ssl › certificate-conversion.gmi captured on 2024-03-21 at 15:58:33. Gemini links have been rewritten to link to archived content
-=-=-=-=-=-=-
X.509 certificates can be stored in a variety of different forms. Worse, the certificates may be housed in a vendor or language specific certificate store that may require writing custom code to extract the certificates. Different forms or certificate stores can complicate migrations from one platform to another. It may be simpler to create new private keys and to make new certificates from those keys rather than trying to preserve the old key and certificate material, though users may then need to deal with a certificate change warning.
The following assumes a unix-shaped system (OpenBSD, mostly) with an OpenSSL-shaped SSL library (LibreSSL, mostly) with an effort to convert certificates from Agate to gemserv.
Converting the certificates to a different form may be difficult if you have no idea where they are located. The location might be indicated in the documentation or configuration for the software; worst case, you may need to use process tracing tools (ktrace, strace, etc.) to see what files a process uses. Commercial software may even store keys and certificates in secure memory or off in some other device on the network. Good luck!
Running the binary directly from the build directory is not a best practice, but that's not important here.
$ ./target/release/agate --content ~/tmp/content/ --hostname localhost [2024-03-20T18:10:44Z INFO agate] The certificate directory ".certificates" does not exist, creating it. [2024-03-20T18:10:44Z INFO agate] No certificate or key found for "localhost", generating them. [2024-03-20T18:10:44Z INFO agate] Started listener on [::]:1965 [2024-03-20T18:10:44Z INFO agate] Started listener on 0.0.0.0:1965
Here one may not know where the ".certificates" directory is; process tracing may better reveal the location.
^C $ ktrace ./target/release/agate --content ~/tmp/content/ --hostname localhost [2024-03-20T18:12:01Z INFO agate] Started listener on 0.0.0.0:1965 [2024-03-20T18:12:01Z INFO agate] Started listener on [::]:1965 ^C $ kdump | grep .cert 53641 agate NAMI ".certificates" 53641 agate NAMI ".certificates/cert.der" 53641 agate NAMI ".certificates/key.der" 53641 agate NAMI ".certificates" 53641 agate NAMI ".certificates/localhost" 53641 agate NAMI ".certificates/localhost/cert.der" 53641 agate NAMI ".certificates/localhost/cert.der" 53641 agate NAMI ".certificates/localhost/key.der" $ rm ktrace.out
This is a relative path location, so will be in the current working directory, or relative to where any prior chdir(2) calls changed the working directory to. There may be a startup script or init system for agate that changes the working directory, in which case you may need to learn more about that init system (e.g. openrc, systemd, etc) and where the configuration or scripts for it are hidden. If the software was built as a port or package, the port system may have tools that tell you what files are part of a package. Good luck!
There are several forms certificates can be saved as; PEM and DER are common on unix systems, though there is also PKCS #12 and so forth. The type may be indicated by a file extension, or a tool like file(1) may be able to guess the file type. Vendors vary in where they hide certificates; the following assumes OpenBSD.
$ file /etc/ssl/* /etc/ssl/cert.pem: ASCII English text /etc/ssl/ikeca.cnf: ASCII English text /etc/ssl/localhost.pem: PEM certificate /etc/ssl/openssl.cnf: ASCII text /etc/ssl/private/: directory /etc/ssl/x509v3.cnf: ASCII English text
To continue the agate example, file(1) on OpenBSD cannot identify the type used by agate:
$ file .certificates/localhost/* .certificates/localhost/cert.der: data .certificates/localhost/key.der: data $ less .certificates/localhost/cert.der ".certificates/localhost/cert.der" may be a binary file. See it anyway? $
The reset(1) command may be handy if you cat(1) a binary file that messes up your terminal. From the extension however we might guess that *.der files are stored in DER format.
SSL libraries typically ship with tools that can convert certificate formats, e.g. the openssl(1) tool that ships with OpenSSL and LibreSSL. If you have some other library, check its documentation for what it offers, or install a library you do know how to use.
OpenSSL has various subcommands for various key and certificate types, so it may take some tries to find the right commands.
$ cd .certificates/localhost/ $ openssl asn1parse -inform der -in cert.der 0:d=0 hl=4 l= 323 cons: SEQUENCE 4:d=1 hl=3 l= 234 cons: SEQUENCE 7:d=2 hl=2 l= 3 cons: cont [ 0 ] 9:d=3 hl=2 l= 1 prim: INTEGER :02 12:d=2 hl=2 l= 20 prim: INTEGER :7158830E8F02A271D21AFB1BFB8A41D5 F45DA2F9 34:d=2 hl=2 l= 10 cons: SEQUENCE 36:d=3 hl=2 l= 8 prim: OBJECT :ecdsa-with-SHA256 46:d=2 hl=2 l= 20 cons: SEQUENCE 48:d=3 hl=2 l= 18 cons: SET 50:d=4 hl=2 l= 16 cons: SEQUENCE 52:d=5 hl=2 l= 3 prim: OBJECT :commonName 57:d=5 hl=2 l= 9 prim: UTF8STRING :localhost ...
So the cert.der file parses as ASN1 assuming the DER format, which is a good indication that it is actually DER. If it does not, one would need to determine what the code has done with the file—encryption? a custom format? actually instead using PEM or some other known format? etc.
$ openssl x509 -inform der -outform pem -in cert.der -out cert.pem $ file cert.pem cert.pem: PEM certificate $ openssl x509 -noout -text < cert.pem Certificate: Data: Version: 3 (0x2) Serial Number: 71:58:83:0e:8f:02:a2:71:d2:1a:fb:1b:fb:8a:41:d5:f4:5d:a2:f9 Signature Algorithm: ecdsa-with-SHA256 Issuer: CN=localhost Validity Not Before: Jan 1 00:00:00 1975 GMT Not After : Jan 1 00:00:00 4096 GMT ...
And we probably need to convert the private key in addition to the certificate information:
$ umask 022 $ umask 077 $ openssl rsa -inform der -outform pem < key.der > key.pem 12468256679856:error:06FFF07F:digital envelope routines:CRYPTO_internal:expecting an rsa key:/usr/src/lib/libcrypto/evp/p_lib.c:445: $ ls -l key.pem -rw------- 1 jhqdoe jhqdoe 0 Mar 20 18:52 key.pem
The umask(1) command is to limit the permissions on the "key.pm" output file. Leaking the keys to other users or processes on the system might be a security risk. Be sure to change it back to the default, to close the shell instance used for the conversion. However, in this case, the "rsa" command failed. Maybe we do not actually have an RSA key here?
$ rm key.pem $ openssl asn1parse -inform der < key.der 0:d=0 hl=3 l= 135 cons: SEQUENCE 3:d=1 hl=2 l= 1 prim: INTEGER :00 6:d=1 hl=2 l= 19 cons: SEQUENCE 8:d=2 hl=2 l= 7 prim: OBJECT :id-ecPublicKey 17:d=2 hl=2 l= 8 prim: OBJECT :prime256v1 27:d=1 hl=2 l= 109 prim: OCTET STRING [HEX DUMP]:...
The "id-ecPublicKey" indicates a newer elliptic curve key, not a more typical RSA key. There is also an older DSA key type. LibreSSL (version 3.8.2 in OpenBSD 7.4) does not support converting EC key, so you may need to find some other SSL library to convert this key type:
cosmic$ openssl version OpenSSL 3.0.2 15 Mar 2022 (Library: OpenSSL 3.0.2 15 Mar 2022) cosmic$ openssl ec -inform der -outform pem < key.der > key.pem read EC key writing EC key cosmic$ file key.pem key.pem: PEM EC private key
So from two agate auto-generated files, we now have PEM versions of those files:
$ umask 022 $ ls cert.der cert.pem key.der key.pem $ file * cert.der: data cert.pem: PEM certificate key.der: data key.pem: PEM EC private key
Another problem is that we may not know what format(s) are supported in the software we intend to migrate to; documentation may be lacking, or there may be too much documentation to wade through. For example "gemserv" is somewhat lacking in documentation, though searching for "DER" or "PEM" in the source may give hints as to what is supported. It may also turn up a lot of noise to wade through.
$ grep -rIi pem src src/lib/tls.rs:use rustls_pemfile::{certs, pkcs8_private_keys};
Using strings(1) on an unknown binary may reveal the function calls used, or not, as well as something that logs function calls used, though those are different from the system functions that ktrace or strace log. It would help to know something of the API involved to help interpret any such output.
A less elegant approach is to try random formats until something works, or you run out of time. With practice you may hone in on things more likely to work. In particular the "pkcs8_private_keys" hints that gemserv may need a PKCS #8 format key file?
$ cd ../gemserv $ cp ../agate/.certificates/localhost/*m . $ cat config interface = [ "[::]:1965" ] [[server]] hostname = "localhost" dir = "/tmp" key = "key.pem" cert = "cert.pem" $ ./target/release/gemserv config 2024-03-20 20:26:03,428 INFO [gemserv] Serving 1 vhosts thread 'main' panicked at 'removal index (is 0) should be < len (is 0)', src/lib/tls.rs:46:42 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
The backtrace (as is usual for a non-programmer, or at least one who does not use Rust) was not useful. Maybe gemserv needs the key in PKCS #8 format?
$ openssl pkcs8 -inform pem -topk8 < key.pem -nocrypt > pk8.pem $ ed config 109 /key key = "key.pem" s/"key/"pk8 key = "pk8.pem" wq 109 $ ./target/release/gemserv config 2024-03-20 20:32:35,311 INFO [gemserv] Serving 1 vhosts Segmentation fault (core dumped)
Well, that's no good. Maybe it's the elliptic curve key; what happens if we generate a more typical RSA key and use that?
$ openssl req -x509 -newkey rsa:2048 -sha256 -days 99999 \ -nodes -keyout loco.key -out loco.cert -subj /CN=localhost \ -addext subjectAltName=DNS:localhost,IP:127.0.0.1 Generating a 2048 bit RSA private key .... ................................... writing new private key to 'loco.key' ----- $ file loco.* loco.cert: PEM certificate loco.key: ASCII text $ cat config interface = [ "[::]:1965" ] [[server]] hostname = "localhost" dir = "/tmp" key = "loco.key" cert = "loco.cert" $ ./target/release/gemserv config 2024-03-20 20:43:56,432 INFO [gemserv] Serving 1 vhosts Illegal instruction (core dumped)
The illegal instruction is probably "rust runs poorly on OpenBSD unless you fiddle with something, check the mailing list archives" though it does look that, probably, the server read the RSA key in PEM format. I would suspect that gemserv may not support the elliptic curve key format used by agate, so therefore one would need to generate a new keypair for use with gemserv, and retire the old key and certificate.
At least that's as far as I got; maybe someone else can go further with this.