diff --git a/docs/content/about/configuration.md b/docs/content/about/configuration.md index d95ea99bd..f7efd2cc9 100644 --- a/docs/content/about/configuration.md +++ b/docs/content/about/configuration.md @@ -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). diff --git a/registry/auth/token/accesscontroller.go b/registry/auth/token/accesscontroller.go index f24a6d0a7..b86cb3673 100644 --- a/registry/auth/token/accesscontroller.go +++ b/registry/auth/token/accesscontroller.go @@ -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 } } diff --git a/registry/auth/token/util.go b/registry/auth/token/util.go index 29b8811fd..88079cce7 100644 --- a/registry/auth/token/util.go +++ b/registry/auth/token/util.go @@ -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) }