💾 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

View Raw

More Information

-=-=-=-=-=-=-

SSL Certificate Conversion

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.

Locate the Certificates

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!

Agate Example

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!

Type of Certificate

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.

Conversion

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

Usage with gemserv

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?

certificates.gmi

    $ 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.

Sources

Agate

gemserv

https://github.com/schrockwell/gemserv-admin