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`:
- 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
[specification](../spec/auth/token.md).

View File

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

View File

@ -3,6 +3,7 @@ package token
import (
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"crypto/sha256"
"encoding/base64"
@ -54,15 +55,26 @@ func hashAndEncode(payload string) string {
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
// are required to be ordered lexicographical order. Golang does not guarantee
// order of keys[0]
// [0]: https://groups.google.com/g/golang-dev/c/zBQwhm3VfvU
//
// The payloads are small enough to create the JSON strings manually
func GetRFC7638Thumbprint(publickey crypto.PublicKey) string {
var payload string
func getJWKThumbprint(publickey crypto.PublicKey, skipED25519 bool) string {
switch pubkey := publickey.(type) {
case *rsa.PublicKey:
e_big := big.NewInt(int64(pubkey.E)).Bytes()
@ -70,17 +82,22 @@ func GetRFC7638Thumbprint(publickey crypto.PublicKey) string {
e := base64.RawURLEncoding.EncodeToString(e_big)
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:
params := pubkey.Params()
crv := params.Name
x := base64.RawURLEncoding.EncodeToString(params.Gx.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:
return ""
}
return hashAndEncode(payload)
}