Why some encryption keys don't work with the jsonwebtoken NPM package

Specifically, PKIX formatted keys.

·

3 min read

One would think that any DER encoded keypair should work with the jsonwebtoken package (which I will from now on just call JWT) from Auth0, but annoying, that is not the case.

See, JWT relies upon Node's crypto library to determine if your key is valid or not... Node does not allow certain DER encoding methods for private & public keys.

Luckily, golang's crypto library only really gives us two DER encoding options for RSA keys. PKCS1 and PKCS8 for private keys, and PKCS1 and PKIX for public keys.
Now the PKCS# keys work just fine on Node, the issue is the PKIX keys.

These keys aren't actually encoded for the same type of use as the PKCS# encoded keys are... they're meant for the internet, specifically, certificates. At least, according to this stackoverflow post from April 17th, 2018.

So, the PKIX keys are a different structure and use case than the PKCS keys.

They're meant for certificates, not simpler keys.
So, don't use them for anything which just needs a key.

How did I figure this out

If you're wondering how I figured this out, it's because Folderr's CLI is written in Go, and I tried using it to setup Folderr's database, and it just kept failing. Folderr would say the key was not asymmetrical, which, apparently it wasn't, because it was meant for a certificate.
There's a simple way to replicate this issue and see it for yourself

  1. Clone https://github.com/Folderr/jwt-keytests

  2. Use something like a modified version of foldcli's GenKeys function (modified to put out both PKIX and PKCS1 public keys)

  3. Try the jwt-keytest with the PKCS1 public key, and PKIX public key


Replication

Example of a modified GenKeys function (generates 1 private key, and 2 public keys):

import (
    "crypto/rand"
    "crypto/rsa"
    "crypto/x509"
    "encoding/pem"
)
// returns Private Key, Public Key (PKCS1), Public Key (PKIX), and error
func GenKeys() ([]byte, []byte, []byte, error) {
    privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
    if err != nil {
        return nil, nil, nil, err
    }
    // Sanity check the privateKey
    if privateKey.Validate() != nil {
        return nil, nil, nil, privateKey.Validate()
    }
    // You can use PKCS1, I'm sure it doesn't matter.
    privBytes, err := x509.MarshalPKCS8PrivateKey(privateKey)
    if err != nil {
        return nil, nil, nil, err
    }
    privBlock := pem.Block{
        Type:    "RSA PRIVATE KEY",
        Headers: nil,
        Bytes:   privBytes,
    }
    privatePem := pem.EncodeToMemory(&privBlock)

    // Works with nodes jsonwebtoken library
    pubBytes := x509.MarshalPKCS1PublicKey(&privateKey.PublicKey)
    pubBlock := pem.Block{
        Type:    "RSA PUBLIC KEY",
        Headers: nil,
        Bytes:   pubBytes,
    }
    publicPem := pem.EncodeToMemory(&pubBlock)

    // Does not work with node's jsonwebtoken library
    pubPKIX, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey)
    if err != nil {
        return nil, nil, nil, err
    }
    pubPKIXBlock := pem.Block{
        Type:    "RSA PUBLIC KEY",
        Headers: nil,
        Bytes:   pubPKIX,
    }
    pubPKIXPem := pem.EncodeToMemory(&pubPKIXBlock)
    return privatePem, publicPem, pubPKIXPem, nil
}

You'll need to manage the saving files yourself, but, this will generate the correct keys

You can use Folderr's JWT Keytest repo to test these keys


Sources

https://stackoverflow.com/a/49878687