package clientassertion

import "github.com/hashicorp/cap/oidc/clientassertion"

Package clientassertion signs JWTs with a Private Key or Client Secret for use in OIDC client_assertion requests, A.K.A. private_key_jwt. reference: https://oauth.net/private-key-jwt/

Index

Examples

Constants

const (
	HS256 HSAlgorithm = "HS256" // HMAC using SHA-256
	HS384 HSAlgorithm = "HS384" // HMAC using SHA-384
	HS512 HSAlgorithm = "HS512" // HMAC using SHA-512
	RS256 RSAlgorithm = "RS256" // RSASSA-PKCS-v1.5 using SHA-256
	RS384 RSAlgorithm = "RS384" // RSASSA-PKCS-v1.5 using SHA-384
	RS512 RSAlgorithm = "RS512" // RSASSA-PKCS-v1.5 using SHA-512
)

JOSE asymmetric signing algorithm values as defined by RFC 7518. See: https://tools.ietf.org/html/rfc7518#section-3.1

const (
	// JWTTypeParam is the proper value for client_assertion_type.
	// https://www.rfc-editor.org/rfc/rfc7523.html#section-2.2
	JWTTypeParam = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
)
const KeyIDHeader = "kid"

KeyIDHeader is the "kid" header on a JWT, which providers use to look up the right public key to verify the JWT.

Variables

var (
	ErrMissingClientID  = errors.New("missing client ID")
	ErrMissingAudience  = errors.New("missing audience")
	ErrMissingAlgorithm = errors.New("missing signing algorithm")
	ErrMissingKeyID     = errors.New("missing key ID")
	ErrMissingKey       = errors.New("missing private key")
	ErrMissingSecret    = errors.New("missing client secret")
	ErrKidHeader        = errors.New(`"kid" not allowed in WithHeaders; use WithKeyID instead`)
	ErrCreatingSigner   = errors.New("error creating jwt signer")

	ErrUnsupportedAlgorithm = errors.New("unsupported algorithm")
	ErrInvalidSecretLength  = errors.New("invalid secret length for algorithm")
	ErrNilPrivateKey        = errors.New("nil private key")
)

Types

type HSAlgorithm

type HSAlgorithm string

HSAlgorithm is an HMAC signature algorithm

func (HSAlgorithm) Validate

func (a HSAlgorithm) Validate(secret string) error

Validate checks that the secret is a supported algorithm and that it's the proper length for the HSAlgorithm:

type JWT

type JWT struct {
	// contains filtered or unexported fields
}

JWT is used to create a client assertion JWT, a special JWT used by an OAuth 2.0 or OIDC client to authenticate themselves to an authorization server

Example

Code:

{
	cid := "client-id"
	aud := []string{"audience"}

	// With an HMAC client secret
	secret := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" // 32 bytes for HS256
	j, err := NewJWTWithHMAC(cid, aud, HS256, secret)
	if err != nil {
		log.Fatal(err)
	}
	signed, err := j.Serialize()
	if err != nil {
		log.Fatal(err)
	}

	{
		// decode and inspect the JWT -- this is the IDP's job,
		// but it illustrates the example.
		token, err := jwt.ParseSigned(signed, []jose.SignatureAlgorithm{"HS256"})
		if err != nil {
			log.Fatal(err)
		}
		headers := token.Headers[0]
		fmt.Printf("ClientSecret\n  Headers - Algorithm: %s; typ: %s\n",
			headers.Algorithm, headers.ExtraHeaders["typ"])
		var claim jwt.Claims
		err = token.Claims([]byte(secret), &claim)
		if err != nil {
			log.Fatal(err)
		}
		fmt.Printf("  Claims  - Issuer: %s; Subject: %s; Audience: %v\n",
			claim.Issuer, claim.Subject, claim.Audience)
	}

	// With an RSA key
	privKey, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		log.Fatal(err)
	}
	pubKey, ok := privKey.Public().(*rsa.PublicKey)
	if !ok {
		log.Fatal("couldn't get rsa.PublicKey from PrivateKey")
	}
	j, err = NewJWTWithRSAKey(cid, aud, RS256, privKey,
		// note: for some providers, they key ID may be an x5t derivation
		// of a cert generated from the private key.
		// if your key has an associated JWKS endpoint, it will be the "kid"
		// for the public key at /.well-known/jwks.json
		WithKeyID("some-key-id"),
		// extra headers, like x5t, are optional
		WithHeaders(map[string]string{
			"x5t": "should-be-derived-from-a-cert",
		}),
	)
	if err != nil {
		log.Fatal(err)
	}
	signed, err = j.Serialize()
	if err != nil {
		log.Fatal(err)
	}

	{ // decode and inspect the JWT -- this is the IDP's job
		token, err := jwt.ParseSigned(signed, []jose.SignatureAlgorithm{"RS256"})
		if err != nil {
			log.Fatal(err)
		}
		h := token.Headers[0]
		fmt.Printf("PrivateKey\n  Headers - KeyID: %s; Algorithm: %s; typ: %s; x5t: %s\n",
			h.KeyID, h.Algorithm, h.ExtraHeaders["typ"], h.ExtraHeaders["x5t"])
		var claim jwt.Claims
		err = token.Claims(pubKey, &claim)
		if err != nil {
			log.Fatal(err)
		}
		fmt.Printf("  Claims  - Issuer: %s; Subject: %s; Audience: %v\n",
			claim.Issuer, claim.Subject, claim.Audience)
	}

	// Output:
	// ClientSecret
	//   Headers - Algorithm: HS256; typ: JWT
	//   Claims  - Issuer: client-id; Subject: client-id; Audience: [audience]
	// PrivateKey
	//   Headers - KeyID: some-key-id; Algorithm: RS256; typ: JWT; x5t: should-be-derived-from-a-cert
	//   Claims  - Issuer: client-id; Subject: client-id; Audience: [audience]
}

Output:

ClientSecret
  Headers - Algorithm: HS256; typ: JWT
  Claims  - Issuer: client-id; Subject: client-id; Audience: [audience]
PrivateKey
  Headers - KeyID: some-key-id; Algorithm: RS256; typ: JWT; x5t: should-be-derived-from-a-cert
  Claims  - Issuer: client-id; Subject: client-id; Audience: [audience]

func NewJWTWithHMAC

func NewJWTWithHMAC(clientID string, audience []string,
	alg HSAlgorithm, secret string, opts ...Option,
) (*JWT, error)

NewJWTWithHMAC creates a new JWT which will be signed with an HMAC secret.

alg must be one of: * HS256 with a >= 32 byte secret * HS384 with a >= 48 byte secret * HS512 with a >= 64 byte secret

Supported Options: * WithKeyID * WithHeaders

func NewJWTWithRSAKey

func NewJWTWithRSAKey(clientID string, audience []string,
	alg RSAlgorithm, key *rsa.PrivateKey, opts ...Option,
) (*JWT, error)

NewJWTWithRSAKey creates a new JWT which will be signed with a private key.

alg must be one of: * RS256 * RS384 * RS512

Supported Options: * WithKeyID * WithHeaders

func (*JWT) Serialize

func (j *JWT) Serialize() (string, error)

Serialize returns client assertion JWT which can be used by an OAuth 2.0 or OIDC client to authenticate themselves to an authorization server

type Option

type Option func(*JWT) error

Option configures the JWT

func WithHeaders

func WithHeaders(h map[string]string) Option

WithHeaders sets extra JWT headers. Do not set a "kid" header here; instead use WithKeyID.

func WithKeyID

func WithKeyID(keyID string) Option

WithKeyID sets the "kid" header that OIDC providers use to look up the public key to check the signed JWT

type RSAlgorithm

type RSAlgorithm string

RSAlgorithm is an RSA signature algorithm

func (RSAlgorithm) Validate

func (a RSAlgorithm) Validate(key *rsa.PrivateKey) error

Validate checks that the key is a supported algorithm and is valid per rsa.PrivateKey's Validate() method.

Source Files

algorithms.go client_assertion.go error.go options.go

Version
v0.9.0 (latest)
Published
Feb 28, 2025
Platform
linux/amd64
Imports
7 packages
Last checked
2 months ago

Tools for package owners.