A Go implementation of the JOSE suite of specifications — JWS (RFC 7515), JWA (RFC 7518), JWK (RFC 7517), and JWT (RFC 7519).
- Go 1.21 or higher
go get github.com/MichaelFraser99/go-joseCurrently, the module supports the following asymmetric algorithms:
- ES256
- ES384
- ES512
- RS256
- RS384
- RS512
- PS256
- PS384
- PS512
Also supported are the following HMAC with SHA2 symmetric algorithms:
- HS256
- HS384
- HS512
Please note Validator objects cannot be created for HMAC algorithms. When creating a new Signer an optional secret key can be passed as []byte. If none provided, a random key is instead generated and can be retrieved with the Public method. This returns an object of type SecretKey defined as so:
type SecretKey []byte
func (s *SecretKey) Equal(x crypto.PublicKey) bool {
secretKey, ok := x.(*SecretKey)
if !ok {
return false
}
return bytes.Equal(*s, *secretKey)
}Algorithms are represented inside this module with the following type:
type Algorithm intwith the following methods defined
func (a Algorithm) String() stringString returns the string representation of that algorithm (i.e. "RS256" or "HS384")
func GetAlgorithm(alg string) *AlgorithmGetAlgorithm takes in a string and returns a pointer to the relevant algorithm type or nil if an invalid string provided
To create a Signer object use the GetSigner method. This takes in an algorithm and an optional Opts object (defines a bit size used for RSA keys) and returns a crypto.Signer implementation complete with generated key pair. The below example shows how to generate a signer for ES256:
signer, err := jose.GetSigner(model.ES256, nil)The GetSignerFromPrivateKey method can also be used. This takes in an algorithm and a pointer to a crypto.PrivateKey implementation. The below example shows how to generate a signer for ES256:
signer, err := jose.GetSignerFromPrivateKey(model.ES256, privateKey)The Sign method follows JOSE conventions per RFC 7518 rather than Go's crypto.Signer contract. It supports two modes:
- JOSE mode (
opts == niloropts.HashFunc() == 0): pass the raw JWS Signing Input — the signer hashes it internally using the algorithm's designated hash. This is the expected usage for JWS operations. - Pre-hashed mode (
opts.HashFunc() != 0): pass an already-hashed digest — the signer skips internal hashing. This supports interoperability with callers following Go'scrypto.Signerconvention.
A SignerOpts implementation is provided:
type SignerOpts struct {
Hash crypto.Hash
}ValidateSignature always expects the raw signing input (not pre-hashed) and hashes internally.
In addition to the packaged signers, a validator type is also included for each algorithm. This can be constructed in one of two ways:
The GetValidator method takes in a crypto.PublicKey implementation and returns a validator instance. The below example shows how to generate a validator for ES256:
// Construct a validator from a public key
validator, err := jose.GetValidator(model.ES256, publicKey)
// Construct a validator from a signer instance
signer, err := jose.GetSigner(model.ES256, nil)
validator, err := jose.GetValidator(signer.Alg(), signer.Public())The GetValidatorFromJwk method takes in the bytes of a jwk format public key and returns a validator instance. The below example shows how to generate a validator for ES256:
// Construct a validator from a jwk public key
validator, err := jose.GetValidatorFromJwk(model.ES256, publicKeyBytes)The validator object has a method ValidateSignature which takes in the bytes of the digest and signature and returns a boolean indicating whether the signature is valid. The below example shows how to validate a signature:
// Validate a signature
validator, err := jose.GetValidator(model.ES256, publicKey)
valid, err := validator.ValidateSignature(digest, signature)Finally, validators expose their PublicKey with the Public() method
validator, err := jose.GetValidator(model.ES256, publicKey)
pk := validator.Public()This library includes two methods for converting keys into JWK format
All JWK methods include in the returned map a KID suggestion based on the public key component
PublicJwk takes in a pointer to a public key and returns a map[string]string containing the jwk representation of the provided public key
// Direct from a public key
jwkMap, err := PublicJwk(publicKey)
// From a Signer
signer, err := jose.GetSigner(model.ES256, nil)
publicKey := signer.Public
jwkMap, err := PublicJwk(&publicKey)Also included is a method to convert an existing public key JWK back into it's respective public key
The value returned is a pointer to the respective public key *ecdsa.PublicKey or *rsa.PublicKey
PublicFromJwk(jwk map[string]any) (crypto.PublicKey, error)This library includes two methods for jwt handling
The first is for signing a jwt from provided signer implementation, head map, and body map
If the head does not include the typ claim, the method will insert a value of "JWT"
Additionally, if the crypto.Signer implementation provided is one of the implementations defined in this module, the alg claim is also added (if not already present)
New(signer crypto.Signer, head, body map[string]any) (*string, error) {The second provides basic jwt validation and returns the decoded head and body values as instances of map[string]any
If present, the iat, nbf, and exp claims will also be validated
Validate(publicKey crypto.PublicKey, jwt string) (head, body map[string]any, err error)This package defines the following errors:
- InvalidSignature - The provided signature does not match the digest
- UnsupportedAlgorithm - The algorithm specified is not currently supported
- InvalidPublicKey - The provided public key is invalid
- SigningError - An error occurred while signing the token