Skip to content

Instantly share code, notes, and snippets.

@monmohan
Last active January 18, 2023 09:02
Show Gist options
  • Save monmohan/d08d41c856a54d7e7619f8fba8afdf44 to your computer and use it in GitHub Desktop.
Save monmohan/d08d41c856a54d7e7619f8fba8afdf44 to your computer and use it in GitHub Desktop.
JWK - Certificate from X5c and validation
package jws
import (
"bytes"
"crypto"
"crypto/rsa"
_ "crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/binary"
"fmt"
"math/big"
"os"
"strings"
)
type JWKS struct {
Keys []JWK
}
type JWK struct {
Alg string
Kty string
X5c []string
N string
E string
Kid string
X5t string
}
//VerifySignature verifies the signature present in JWT using the public key information in JWK
func VerifySignature(jwtToken string, jwk *JWK) error {
parts := strings.Split(jwtToken, ".")
message := []byte(strings.Join(parts[0:2], "."))
signature, err := base64.RawURLEncoding.DecodeString(parts[2])
if err != nil {
return err
}
publicKey := GetPublicKeyFromModulusAndExponent(jwk)
// Only small messages can be signed directly; thus the hash of a
// message, rather than the message itself, is signed.
hasher := crypto.SHA256.New()
hasher.Write(message)
err = rsa.VerifyPKCS1v15(publicKey, crypto.SHA256, hasher.Sum(nil), signature)
return err
}
//GetPublicKeyFromModulusAndExponent returns a rsa.PublicKey based on n and e members in JWK JSON
func GetPublicKeyFromModulusAndExponent(jwk *JWK) *rsa.PublicKey {
n, _ := base64.RawURLEncoding.DecodeString(jwk.N)
e, _ := base64.RawURLEncoding.DecodeString(jwk.E)
z := new(big.Int)
z.SetBytes(n)
//decoding key.E returns a three byte slice, https://golang.org/pkg/encoding/binary/#Read and other conversions fail
//since they are expecting to read as many bytes as the size of int being returned (4 bytes for uint32 for example)
var buffer bytes.Buffer
buffer.WriteByte(0)
buffer.Write(e)
exponent := binary.BigEndian.Uint32(buffer.Bytes())
return &rsa.PublicKey{N: z, E: int(exponent)}
}
//ReadCertificatefromJWK reads the member x5c[0] from JWK and return as x509.Certificate
func ReadCertificatefromJWK(jwk *JWK) (*x509.Certificate, error) {
derBytesCert, err := base64.StdEncoding.DecodeString(jwk.X5c[0])
if err != nil {
fmt.Printf("Decode err %s\n", err.Error())
return nil, err
}
cert, err := x509.ParseCertificate(derBytesCert)
if err != nil {
fmt.Printf("x509 parse err %s\n", err.Error())
return nil, err
}
return cert, nil
}
//writeCertAsDer writes the member x5c[0] decoded bytes (DER) to the disk as file
func writeCertAsDer(jwk *JWK) error {
derBytesCert, err := base64.StdEncoding.DecodeString(jwk.X5c[0])
if err != nil {
fmt.Printf("Decode err %s\n", err.Error())
return err
}
certFile, _ := os.Create("x5c.der")
certFile.Write(derBytesCert)
return nil
}
package jws
import (
"bytes"
"crypto/rsa"
"encoding/json"
"os"
"testing"
)
func TestPublicKey(t *testing.T) {
jwtToken := "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik5FTXlNRUZDTXpVd01URTFRVE5CT1VGRE1FUTFPRGN6UmprNU56QkdRelk0UVRrMVEwWkVPUSJ9.eyJpc3MiOiJodHRwczovL2Rldi1lanRsOTg4dy5hdXRoMC5jb20vIiwic3ViIjoiZ1pTeXNwQ1k1ZEk0aDFaM3Fwd3BkYjlUNFVQZEdENWtAY2xpZW50cyIsImF1ZCI6Imh0dHA6Ly9oZWxsb3dvcmxkIiwiaWF0IjoxNTcyNDA2NDQ3LCJleHAiOjE1NzI0OTI4NDcsImF6cCI6ImdaU3lzcENZNWRJNGgxWjNxcHdwZGI5VDRVUGRHRDVrIiwiZ3R5IjoiY2xpZW50LWNyZWRlbnRpYWxzIn0.nupgm7iFqSnERq9GxszwBrsYrYfMuSfUGj8tGQlkY3Ksh3o_IDfq1GO5ngHQLZuYPD-8qPIovPBEVomGZCo_jYvsbjmYkalAStmF01TvSoXQgJd09ygZstH0liKsmINStiRE8fTA-yfEIuBYttROizx-cDoxiindbKNIGOsqf6yOxf7ww8DrTBJKYRnHVkAfIK8wm9LRpsaOVzWdC7S3cbhCKvANjT0RTRpAx8b_AOr_UCpOr8paj-xMT9Zc9HVCMZLBfj6OZ6yVvnC9g6q_SlTa--fY9SL5eqy6-q1JGoyK_-BQ_YrCwrRdrjoJsJ8j-XFRFWJX09W3oDuZ990nGA"
jwk, err := readJWKFromFile("./jwks.json")
if err != nil {
t.Fatalf("Error in reading JWK %s", err.Error())
}
err = VerifySignature(jwtToken, jwk)
if err != nil {
t.Fatalf("Failed signature verification : %s", err)
}
}
func TestValidateJWK(t *testing.T) {
jwk, err := readJWKFromFile("./jwks.json")
if err != nil {
t.Fatalf("Error in reading JWK %s", err.Error())
}
cert, err := ReadCertificatefromJWK(jwk)
if err != nil {
t.Fatalf("Error in validating JWK %s", err.Error())
}
//get public key from cert and assert that we have a RSA Public Key
rsaKeyCert := cert.PublicKey.(*rsa.PublicKey)
//compare with the public key from modulus and exponent in JWK
rsaFromJWK := GetPublicKeyFromModulusAndExponent(jwk)
if (bytes.Compare(rsaKeyCert.N.Bytes(), rsaFromJWK.N.Bytes())) != 0 {
t.Fatal("Modulus from Public key in Certificate is not the same as one in JWK member n")
}
if rsaKeyCert.E != rsaFromJWK.E {
t.Fatalf("Exponent from Public key in Certificate is not the same as one in JWK member e, %d, %d", rsaKeyCert.E, rsaFromJWK.E)
}
}
func readJWKFromFile(fileName string) (*JWK, error) {
jwksFile, err := os.Open(fileName)
if err != nil {
return nil, err
}
dec := json.NewDecoder(jwksFile)
var jwks JWKS
if err := dec.Decode(&jwks); err != nil {
return nil, err
}
return &jwks.Keys[0], nil
}
{
"keys": [
{
"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"
}
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment