pkg/internal/pop/authnscheme.go (104 lines of code) (raw):
// Disclaimer: The PoPAuthenticationScheme implementation of the MSAL AuthenticationScheme
// interface is intended for the usage of Azure Arc.
package pop
import (
"crypto/sha256"
"encoding/base64"
"fmt"
"strings"
"time"
"github.com/google/uuid"
)
// type of a PoP token, as opposed to "JWT" for a regular bearer token
const popTokenType = "pop"
// PoPAuthenticationScheme is a PoP token implementation of the MSAL AuthenticationScheme interface
// used by the Azure Arc Platform team.
// This implementation will only use the passed-in u-claim (representing the ARM ID of the
// cluster/host); other claims passed in during a PoP token request will be disregarded
type PoPAuthenticationScheme struct {
// host is the u claim we will add on the pop token
Host string
PoPKey PoPKey
}
// TokenRequestParams returns the params to use when sending a request for a PoP token
func (as *PoPAuthenticationScheme) TokenRequestParams() map[string]string {
return map[string]string{
"token_type": popTokenType,
"req_cnf": as.PoPKey.ReqCnf(),
}
}
// KeyID returns the key used to sign the PoP token
func (as *PoPAuthenticationScheme) KeyID() string {
return as.PoPKey.KeyID()
}
// FormatAccessToken takes an access token, formats it as a PoP token,
// and returns it as a base-64 encoded string
func (as *PoPAuthenticationScheme) FormatAccessToken(accessToken string) (string, error) {
timestamp := time.Now().Unix()
nonce := uuid.NewString()
nonce = strings.ReplaceAll(nonce, "-", "")
return as.FormatAccessTokenWithOptions(accessToken, nonce, timestamp)
}
// FormatAccessTokenWithOptions takes an access token, nonce, and timestamp, formats
// the token as a PoP token containing the given fields, and returns it as a
// base-64 encoded string
func (as *PoPAuthenticationScheme) FormatAccessTokenWithOptions(accessToken, nonce string, timestamp int64) (string, error) {
header := header{
typ: popTokenType,
alg: as.PoPKey.Alg(),
kid: as.PoPKey.KeyID(),
}
payload := payload{
at: accessToken,
ts: timestamp,
host: as.Host,
jwk: as.PoPKey.JWK(),
nonce: nonce,
}
popAccessToken, err := createPoPAccessToken(header, payload, as.PoPKey)
if err != nil {
return "", fmt.Errorf("error formatting PoP token: %w", err)
}
return popAccessToken.ToBase64(), nil
}
// AccessTokenType returns the PoP access token type
func (as *PoPAuthenticationScheme) AccessTokenType() string {
return popTokenType
}
// type representing the header of a PoP access token
type header struct {
typ string
alg string
kid string
}
// ToString returns a string representation of a header object
func (h *header) ToString() string {
return fmt.Sprintf(`{"typ":"%s","alg":"%s","kid":"%s"}`, h.typ, h.alg, h.kid)
}
// ToBase64 returns a base-64 encoded string representation of a header object
func (h *header) ToBase64() string {
return base64.RawURLEncoding.EncodeToString([]byte(h.ToString()))
}
// type representing the payload of a PoP token
type payload struct {
at string
ts int64
host string
jwk string
nonce string
}
// ToString returns a string representation of a payload object
func (p *payload) ToString() string {
return fmt.Sprintf(`{"at":"%s","ts":%d,"u":"%s","cnf":{"jwk":%s},"nonce":"%s"}`, p.at, p.ts, p.host, p.jwk, p.nonce)
}
// ToBase64 returns a base-64 encoded representation of a payload object
func (p *payload) ToBase64() string {
return base64.RawURLEncoding.EncodeToString([]byte(p.ToString()))
}
// type representing the signature of a PoP token
type signature struct {
sig []byte
}
// ToBase64 returns a base-64 encoded representation of a signature object
func (s *signature) ToBase64() string {
return base64.RawURLEncoding.EncodeToString(s.sig)
}
// type representing a PoP access token
type popAccessToken struct {
Header header
Payload payload
Signature signature
}
// given a header, payload, and PoP key, creates the signature for the token and returns
// a PoPAccessToken object representing the signed token
func createPoPAccessToken(h header, p payload, popKey PoPKey) (*popAccessToken, error) {
token := &popAccessToken{
Header: h,
Payload: p,
}
h256 := sha256.Sum256([]byte(h.ToBase64() + "." + p.ToBase64()))
sig, err := popKey.Sign(h256[:])
if err != nil {
return nil, err
}
token.Signature = signature{
sig: sig,
}
return token, nil
}
// ToBase64 returns a base-64 encoded representation of a PoP access token
func (p *popAccessToken) ToBase64() string {
return fmt.Sprintf("%s.%s.%s", p.Header.ToBase64(), p.Payload.ToBase64(), p.Signature.ToBase64())
}