Last active
January 18, 2023 09:02
-
-
Save monmohan/d08d41c856a54d7e7619f8fba8afdf44 to your computer and use it in GitHub Desktop.
JWK - Certificate from X5c and validation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"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