Validating RSA signature for a JWS — More about JWK and Certificates

msingh
4 min readNov 12, 2019

--

Previously

In the previous article, we looked at how a JWS RSA signature can be validated by fetching information about the public key via a JWK. We overlooked certain aspects which we will discuss in this article to get a deeper understanding.

So lets take a look again at our JWK, which defined the key used to sign the sample JWT we had -

{"alg": "RS256","kty": "RSA","use": "sig","x5c": ["MIIDBzCCAe+gAwIBAgIJakoPho0MJr56MA0GCSqGSIb3DQEBCwUAMCExHzAdBgNVBAMTFmRldi1lanRsOTg4dy5hdXRoMC5jb20wHhcNMTkxMDI5MjIwNzIyWhcNMzMwNzA3MjIwNzIyWjAhMR8wHQYDVQQDExZkZXYtZWp0bDk4OHcuYXV0aDAuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzkM1QHcP0v8bmwQ2fd3Pj6unCTx5k8LsW9cuLtUhAjjzRGpSEwGCKEgi1ej2+0Cxcs1t0wzhO+zSv1TJbsDI0x862PIFEs3xkGqPZU6rfQMzvCmncAcMjuW7r/Zewm0s58oRGyic1Oyp8xiy78czlBG03jk/+/vdttJkie8pUc9AHBuMxAaV4iPN3zSi/J5OVSlovk607H3AUiL3Bfg4ssS1bsJvaFG0kuNscoiP+qLRTjFK6LzZS99VxegeNzttqGbtj5BwNgbtuzrIyfLmYB/9VgEw+QdaQHvxoAvD0f7aYsaJ1R6rrqxo+1Pun7j1/h7kOCGB0UcHDLDw7gaP/wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQwIoo6QzzUL/TcNVpLGrLdd3DAIzAOBgNVHQ8BAf8EBAMCAoQwDQYJKoZIhvcNAQELBQADggEBALb8QycRmauyC/HRWRxTbl0w231HTAVYizQqhFQFl3beSQIhexGik+H+B4ve2rv94QRD3LlraUp+J26wLG89EnSCuCo/OxPAq+lxO6hNf6oKJ+Y2f48awIOxolO0f89qX3KMIkABXwKbYUcd+SBHX5ZP1V9cvJEyH0s3Fq9ObysPCH2j2Hjgz3WMIffSFMaO0DIfh3eNnv9hKQwavUO7fL/jqhBl4QxI2gMySi0Ni7PgAlBgxBx6YUp59q/lzMgAf19GOEOvI7l4dA0bc9pdsm7OhimskvOUSZYi5Pz3n/i/cTVKKhlj6NyINkMXlXGgyM9vEBpdcIpOWn/1H5QVy8Q="],"n": "zkM1QHcP0v8bmwQ2fd3Pj6unCTx5k8LsW9cuLtUhAjjzRGpSEwGCKEgi1ej2-0Cxcs1t0wzhO-zSv1TJbsDI0x862PIFEs3xkGqPZU6rfQMzvCmncAcMjuW7r_Zewm0s58oRGyic1Oyp8xiy78czlBG03jk_-_vdttJkie8pUc9AHBuMxAaV4iPN3zSi_J5OVSlovk607H3AUiL3Bfg4ssS1bsJvaFG0kuNscoiP-qLRTjFK6LzZS99VxegeNzttqGbtj5BwNgbtuzrIyfLmYB_9VgEw-QdaQHvxoAvD0f7aYsaJ1R6rrqxo-1Pun7j1_h7kOCGB0UcHDLDw7gaP_w","e": "AQAB","kid": "NEMyMEFCMzUwMTE1QTNBOUFDMEQ1ODczRjk5NzBGQzY4QTk1Q0ZEOQ","x5t": "NEMyMEFCMzUwMTE1QTNBOUFDMEQ1ODczRjk5NzBGQzY4QTk1Q0ZEOQ"}

We talked about the obvious members like algorithm ( alg) which represents the signing algorithm (RSA + SHA 256) and key type ( kty) represents that the type of key is RSA . We also talked about the modulus ( n) and exponent ( e) which together are sufficient to construct the RSA Public Key. This key can be then used to validate the signature.

So the question is , what is the additional information in the member x5c?

Let’s see what does the JWK RFC ( RFC 7517 )says —

The x5c (X.509 certificate chain) parameter contains a chain of one or more PKIX certificates [RFC5280]. The certificate chain is represented as a JSON array of certificate value strings. Each string in the array is a base64-encoded (Section 4 of [RFC4648] — not base64url-encoded) DER [ITU.X690.1994] PKIX certificate value. The PKIX certificate containing the key value MUST be the first certificate. This MAY be followed by additional certificates, with each subsequent certificate being the one used to certify the previous one. The key in the first certificate MUST match the public key represented by other members of the JWK. Use of this member is OPTIONAL.

Okay. So lets break it down

  1. x5c means x509 certificate chain, represented as an array of certificates
  2. The first certificate in the array is the one that contains the public key represented by this JWK

Our example has only one entry so now we know that this entry which is a string, represents a PKIX certificate.

Let us look at the end result first and then work backwards :

Deciphering the x5c string

Each string in the array is a base64-encoded (Section 4 of [RFC4648] — not base64url-encoded) DER [ITU.X690.1994] PKIX certificate value.

Here is the certificate created from the string -

Certificate from x5c string

And here is what we did to get this -

  • First, Base64 decode the string
derBytesCert, err := base64.StdEncoding.DecodeString(jwk.X5c[0])
  • Then, write the bytes decoded to disk as a file called x5c.der
certFile, _ := os.Create("x5c.der")certFile.Write(derBytesCert)
  • Now read the resulting file using openssl utility
# Command line from my mac
$ openssl x509 -in x5c.der -inform DER -text

Lets look at the details -

The Certificate

  • Certificates essentially contain a Public Key and some metadata like the issuer, subject (owner of the key) and validity. X509 [RFC 5280]/PKIX are standards that define the format and usage of certificates.
  • In the certificate constructed earlier, we have Subject: CN=dev-ejtl988w.auth0.com which is the test tenant I created in Auth0 account. It is the entity the certificate is associated with (the owner) and it can be anything, a hostname , an organization or an individual.
  • Note that the Issuer: CN=dev-ejtl988w.auth0.com is same as the subject which indicates that this is a self signed certificate . Typically, a certificate is itself signed by a certificate authority (CA) using CA’s private key. This verifies the authenticity of the certificate (that’s a topic for another day 😄) .
  • We can also see that the Certificate lists the public key details, notably the algorithm, the size (2048 bits), the Modulus (displayed in Hex) and Exponent.

The OpenSSL Utility

OpenSSL is a full-featured toolkit for the Transport Layer Security (TLS) and Secure Sockets Layer (SSL) protocols but can also be used as a general purpose crypto library. We use openssl binary to decode a DER bytes representing the certificates. OpenSSL isn’t the only tool to decode DER , we could also do that using golang crypto library with a simple line like this -

cert, err := x509.ParseCertificate(derBytesCert)

The DER encoding

Lets look at this structure -

Certificate  ::=  SEQUENCE  {
tbsCertificate TBSCertificate,
signatureAlgorithm SignatureAlgorithm,
signature BIT STRING }

TBSCertificate ::= SEQUENCE {
version [0] Version DEFAULT v1,
serialNumber CertificateSerialNumber,
signature SignatureAlgorithm,
issuer Name,
validity Validity,
subject Name,
subjectPublicKeyInfo SubjectPublicKeyInfo,
issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
-- If present, version MUST be v2 or v3
subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
-- If present, version MUST be v2 or v3
extensions [3] Extensions OPTIONAL
-- If present, version MUST be v3 -- }

Its an abstract definition of the Certificate structure and if you look closely, the structure of the certificate data displayed by the OpenSSL utility resembles this notation.

A Certificate itself is defined as a sequence of Objects — 1) A TBS (short for To Be Signed) Certificate, 2) the SignatureAlgorithm used to sign the certificate by the issuer and 3) the signature itself, which is computed on the encoded TBSCertificate.

The TBSCertificate describes other objects like Subject, Issuer and PublicKey (SubjectPublicKeyInfo).This notation is called ASN.1 . And DER is a binary encoding of the ASN.1 object representation of a given certificate.

Finally the x5c string

So we have Certificate → as ASN1 → binary encoded → DER bytes. What we have in x5c array entry is Base64 encoding of DER bytes. It is a binary to text encoding, so that binary data can be sent across channels which can only handle text (ASCII).

Finally to tie it all together, below is a golang code sample which does the following

  1. Reads the x5c[0] member and parses it into a Certificate
  2. Reads the public key from that cert
  3. Creates a public key from the modulus (member n) and exponent (member e) of the JWK
  4. Compares that the two keys are indeed the same
JWK — Certificate from X5c and validation

--

--