CVE-2025-24976
Description
Distribution is a toolkit to pack, ship, store, and deliver container content. Systems running registry versions 3.0.0-beta.1 through 3.0.0-rc.2 with token authentication enabled may be vulnerable to an issue in which token authentication allows an attacker to inject an untrusted signing key in a JSON web token (JWT). The issue lies in how the JSON web key (JWK) verification is performed. When a JWT contains a JWK header without a certificate chain, the code only checks if the KeyID (kid) matches one of the trusted keys, but doesn't verify that the actual key material matches. A fix for the issue is available at commit 5ea9aa028db65ca5665f6af2c20ecf9dc34e5fcd and expected to be a part of version 3.0.0-rc.3. There is no way to work around this issue without patching if the system requires token authentication.
Patches
1f4a500caf681Fix registry token authentication bug
2 files changed · +57 −2
registry/auth/token/token.go+3 −2 modified@@ -219,11 +219,12 @@ func verifyJWK(header jose.Header, verifyOpts VerifyOptions) (signingKey crypto. // Check to see if the key includes a certificate chain. if len(jwk.Certificates) == 0 { // The JWK should be one of the trusted root keys. - if _, trusted := verifyOpts.TrustedKeys[jwk.KeyID]; !trusted { + trustedKey, trusted := verifyOpts.TrustedKeys[jwk.KeyID] + if !trusted { return nil, errors.New("untrusted JWK with no certificate chain") } // The JWK is one of the trusted keys. - return + return trustedKey, nil } opts := x509.VerifyOptions{
registry/auth/token/token_test.go+54 −0 modified@@ -646,3 +646,57 @@ func TestNewAccessControllerPemBlock(t *testing.T) { t.Fatal("accessController has the wrong number of certificates") } } + +// This test makes sure the untrusted key can not be used in token verification. +func TestVerifyJWKWithTrustedKey(t *testing.T) { + // Generate a test key pair + privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatal(err) + } + pubKey := privKey.Public() + + // Create a JWK with no certificates + jwk := &jose.JSONWebKey{ + Key: privKey, + KeyID: "test-key-id", + Use: "sig", + Algorithm: string(jose.ES256), + } + + // Create verify options with our public key as trusted + verifyOpts := VerifyOptions{ + TrustedKeys: map[string]crypto.PublicKey{ + "test-key-id": pubKey, + }, + } + + // Create test header + header := jose.Header{ + JSONWebKey: jwk, + } + + // Test the verifyJWK function + returnedKey, err := verifyJWK(header, verifyOpts) + if err != nil { + t.Fatalf("Expected no error, got: %v", err) + } + + // Verify the returned key matches our trusted key + if returnedKey != pubKey { + t.Error("Returned key does not match the trusted key") + } + + // Test with untrusted key + verifyOpts.TrustedKeys = map[string]crypto.PublicKey{ + "different-key-id": pubKey, + } + + _, err = verifyJWK(header, verifyOpts) + if err == nil { + t.Error("Expected error for untrusted key, got none") + } + if err.Error() != "untrusted JWK with no certificate chain" { + t.Errorf("Expected 'untrusted JWK with no certificate chain' error, got: %v", err) + } +}
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4News mentions
0No linked articles in our index yet.