fix: implement JWK thumbprint for Ed25519 public keys (#4626)

This commit is contained in:
Milos Gajdos 2025-04-23 08:28:25 -07:00 committed by GitHub
commit e827ce2772
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 26 additions and 9 deletions

View File

@ -690,7 +690,7 @@ Default `signingalgorithms`:
Additional notes on `rootcertbundle`: Additional notes on `rootcertbundle`:
- The public key of this certificate will be automatically added to the list of known keys. - The public key of this certificate will be automatically added to the list of known keys.
- The public key will be identified by it's [RFC7638 Thumbprint](https://datatracker.ietf.org/doc/html/rfc7638). - The public key will be identified by its JWK Thumbprint. See [RFC 7638](https://datatracker.ietf.org/doc/html/rfc7638) and [RFC 8037](https://datatracker.ietf.org/doc/html/rfc8037) for reference.
For more information about Token based authentication configuration, see the For more information about Token based authentication configuration, see the
[specification](../spec/auth/token.md). [specification](../spec/auth/token.md).

View File

@ -351,7 +351,7 @@ func newAccessController(options map[string]interface{}) (auth.AccessController,
rootPool := x509.NewCertPool() rootPool := x509.NewCertPool()
for _, rootCert := range rootCerts { for _, rootCert := range rootCerts {
rootPool.AddCert(rootCert) rootPool.AddCert(rootCert)
if key := GetRFC7638Thumbprint(rootCert.PublicKey); key != "" { if key := GetJWKThumbprint(rootCert.PublicKey); key != "" {
trustedKeys[key] = rootCert.PublicKey trustedKeys[key] = rootCert.PublicKey
} }
} }

View File

@ -3,6 +3,7 @@ package token
import ( import (
"crypto" "crypto"
"crypto/ecdsa" "crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa" "crypto/rsa"
"crypto/sha256" "crypto/sha256"
"encoding/base64" "encoding/base64"
@ -54,15 +55,26 @@ func hashAndEncode(payload string) string {
return base64.RawURLEncoding.EncodeToString(shasum[:]) return base64.RawURLEncoding.EncodeToString(shasum[:])
} }
// Deprecated: use GetJWKThumbprint instead.
func GetRFC7638Thumbprint(publickey crypto.PublicKey) string {
return getJWKThumbprint(publickey, true)
}
// GetJWKThumbprint calculates the JWK thumbprint of a public key.
// The current implementation uses SHA256, but this algorithm may change
// in the future as cryptographic best practices evolve. It returns an
// empty string if the public key type is not supported.
func GetJWKThumbprint(publickey crypto.PublicKey) string {
return getJWKThumbprint(publickey, false)
}
// RFC7638 states in section 3 sub 1 that the keys in the JSON object payload // RFC7638 states in section 3 sub 1 that the keys in the JSON object payload
// are required to be ordered lexicographical order. Golang does not guarantee // are required to be ordered lexicographical order. Golang does not guarantee
// order of keys[0] // order of keys[0]
// [0]: https://groups.google.com/g/golang-dev/c/zBQwhm3VfvU // [0]: https://groups.google.com/g/golang-dev/c/zBQwhm3VfvU
// //
// The payloads are small enough to create the JSON strings manually // The payloads are small enough to create the JSON strings manually
func GetRFC7638Thumbprint(publickey crypto.PublicKey) string { func getJWKThumbprint(publickey crypto.PublicKey, skipED25519 bool) string {
var payload string
switch pubkey := publickey.(type) { switch pubkey := publickey.(type) {
case *rsa.PublicKey: case *rsa.PublicKey:
e_big := big.NewInt(int64(pubkey.E)).Bytes() e_big := big.NewInt(int64(pubkey.E)).Bytes()
@ -70,17 +82,22 @@ func GetRFC7638Thumbprint(publickey crypto.PublicKey) string {
e := base64.RawURLEncoding.EncodeToString(e_big) e := base64.RawURLEncoding.EncodeToString(e_big)
n := base64.RawURLEncoding.EncodeToString(pubkey.N.Bytes()) n := base64.RawURLEncoding.EncodeToString(pubkey.N.Bytes())
payload = fmt.Sprintf(`{"e":"%s","kty":"RSA","n":"%s"}`, e, n) return hashAndEncode(fmt.Sprintf(`{"e":"%s","kty":"RSA","n":"%s"}`, e, n))
case *ecdsa.PublicKey: case *ecdsa.PublicKey:
params := pubkey.Params() params := pubkey.Params()
crv := params.Name crv := params.Name
x := base64.RawURLEncoding.EncodeToString(params.Gx.Bytes()) x := base64.RawURLEncoding.EncodeToString(params.Gx.Bytes())
y := base64.RawURLEncoding.EncodeToString(params.Gy.Bytes()) y := base64.RawURLEncoding.EncodeToString(params.Gy.Bytes())
payload = fmt.Sprintf(`{"crv":"%s","kty":"EC","x":"%s","y":"%s"}`, crv, x, y) return hashAndEncode(fmt.Sprintf(`{"crv":"%s","kty":"EC","x":"%s","y":"%s"}`, crv, x, y))
case ed25519.PublicKey:
if skipED25519 {
return ""
}
x := base64.RawURLEncoding.EncodeToString(pubkey)
return hashAndEncode(fmt.Sprintf(`{"crv":"Ed25519","kty":"OTP","x":"%s"}`, x))
default: default:
return "" return ""
} }
return hashAndEncode(payload)
} }