pkg/internal/pop/poptoken.go (90 lines of code) (raw):
package pop
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"encoding/base64"
"fmt"
"math/big"
)
// PoPKey is a generic interface for PoP key properties and methods
type PoPKey interface {
// encryption/signature algo
Alg() string
// kid
KeyID() string
// jwk that can be embedded in JWT w/ PoP token's cnf claim
JWK() string
// https://tools.ietf.org/html/rfc7638 compliant jwk thumbprint
JWKThumbprint() string
// req_cnf claim that can be included in access token request to AAD
ReqCnf() string
// sign payload using private key
Sign([]byte) ([]byte, error)
}
// software based pop key implementation of PoPKey
type SwKey struct {
key *rsa.PrivateKey
keyID string
jwk string
jwkTP string
reqCnf string
}
// Alg returns the algorithm used to encrypt/sign the SwKey
func (swk *SwKey) Alg() string {
return "RS256"
}
// KeyID returns the keyID of the SwKey, representing the key used to sign the SwKey
func (swk *SwKey) KeyID() string {
return swk.keyID
}
// JWK returns the JSON Web Key of the given SwKey
func (swk *SwKey) JWK() string {
return swk.jwk
}
// JWKThumbprint returns the JWK thumbprint of the given SwKey
func (swk *SwKey) JWKThumbprint() string {
return swk.jwkTP
}
// ReqCnf returns the req_cnf claim to send to AAD for the given SwKey
func (swk *SwKey) ReqCnf() string {
return swk.reqCnf
}
// Sign uses the given SwKey to sign the given payload and returns the signed payload
func (swk *SwKey) Sign(payload []byte) ([]byte, error) {
return swk.key.Sign(rand.Reader, payload, crypto.SHA256)
}
// init initializes the given SwKey using the given private key
func (swk *SwKey) init(key *rsa.PrivateKey) {
swk.key = key
eB64, nB64 := getRSAKeyExponentAndModulus(key)
swk.jwkTP = computeJWKThumbprint(eB64, nB64)
swk.reqCnf = getReqCnf(swk.jwkTP)
// set keyID to jwkTP
swk.keyID = swk.jwkTP
// compute JWK to be included in JWT w/ PoP token's cnf claim
// - https://tools.ietf.org/html/rfc7800#section-3.2
swk.jwk = getJWK(eB64, nB64, swk.keyID)
}
// generateSwKey generates a new SwKey and initializes it with required fields before returning it
func generateSwKey(key *rsa.PrivateKey) (*SwKey, error) {
swk := &SwKey{}
swk.init(key)
return swk, nil
}
// GetSwPoPKey generates a new PoP key returns it
func GetSwPoPKey() (*SwKey, error) {
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, fmt.Errorf("error generating RSA private key: %w", err)
}
return GetSwPoPKeyWithRSAKey(key)
}
func GetSwPoPKeyWithRSAKey(rsaKey *rsa.PrivateKey) (*SwKey, error) {
key, err := generateSwKey(rsaKey)
if err != nil {
return nil, fmt.Errorf("unable to generate PoP key. err: %w", err)
}
return key, nil
}
// getRSAKeyExponentAndModulus returns the exponent and modulus from the given RSA key
// as base-64 encoded strings
func getRSAKeyExponentAndModulus(rsaKey *rsa.PrivateKey) (string, string) {
pubKey := rsaKey.PublicKey
e := big.NewInt(int64(pubKey.E))
eB64 := base64.RawURLEncoding.EncodeToString(e.Bytes())
n := pubKey.N
nB64 := base64.RawURLEncoding.EncodeToString(n.Bytes())
return eB64, nB64
}
// computeJWKThumbprint returns a computed JWK thumbprint using the given base-64 encoded
// exponent and modulus
func computeJWKThumbprint(eB64 string, nB64 string) string {
// compute JWK thumbprint
// jwk format - e, kty, n - in lexicographic order
// - https://tools.ietf.org/html/rfc7638#section-3.3
// - https://tools.ietf.org/html/rfc7638#section-3.1
jwk := fmt.Sprintf(`{"e":"%s","kty":"RSA","n":"%s"}`, eB64, nB64)
jwkS256 := sha256.Sum256([]byte(jwk))
return base64.RawURLEncoding.EncodeToString(jwkS256[:])
}
// getReqCnf computes and returns the value for the req_cnf claim to include when sending
// a request for the token
func getReqCnf(jwkTP string) string {
// req_cnf - base64URL("{"kid":"jwkTP","xms_ksl":"sw"}")
reqCnfJSON := fmt.Sprintf(`{"kid":"%s","xms_ksl":"sw"}`, jwkTP)
return base64.RawURLEncoding.EncodeToString([]byte(reqCnfJSON))
}
// getJWK computes the JWK to be included in the PoP token's enclosed cnf claim and returns it
func getJWK(eB64 string, nB64 string, keyID string) string {
// compute JWK to be included in JWT w/ PoP token's cnf claim
// - https://tools.ietf.org/html/rfc7800#section-3.2
return fmt.Sprintf(`{"e":"%s","kty":"RSA","n":"%s","alg":"RS256","kid":"%s"}`, eB64, nB64, keyID)
}