diff --git a/go.mod b/go.mod index cc509bd768a..64ebd34c0a4 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,7 @@ require ( github.com/docker/go-units v0.5.0 github.com/emicklei/go-restful/v3 v3.14.0 github.com/fsnotify/fsnotify v1.9.0 - github.com/go-jose/go-jose/v4 v4.1.7 + github.com/go-jose/go-jose/v4 v4.1.9 github.com/go-logr/logr v1.4.4 github.com/go-openapi/jsonreference v1.0.2 github.com/godbus/dbus/v5 v5.2.4 diff --git a/go.sum b/go.sum index 86290394458..932d0618c85 100644 --- a/go.sum +++ b/go.sum @@ -109,8 +109,8 @@ github.com/gkampitakis/go-snaps v0.5.15 h1:amyJrvM1D33cPHwVrjo9jQxX8g/7E2wYdZ+01 github.com/gkampitakis/go-snaps v0.5.15/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc= github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= -github.com/go-jose/go-jose/v4 v4.1.7 h1:Nac/98uT5LPkfbiFuYa8ktf62a2VxwQ4xbq1GK8pV5E= -github.com/go-jose/go-jose/v4 v4.1.7/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= +github.com/go-jose/go-jose/v4 v4.1.9 h1:pKfeQkig1NmgK4hJrZYXLEYURXfoJB61Larw0uIW4gY= +github.com/go-jose/go-jose/v4 v4.1.9/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= diff --git a/pkg/features/kube_features.go b/pkg/features/kube_features.go index b8c6fadad98..ddd6ef2147d 100644 --- a/pkg/features/kube_features.go +++ b/pkg/features/kube_features.go @@ -1033,6 +1033,12 @@ const ( // kubelet can request a service account token for. ServiceAccountNodeAudienceRestriction featuregate.Feature = "ServiceAccountNodeAudienceRestriction" + // owner: @everettraven + // issue: https://github.com/kubernetes/kubernetes/issues/141838 + // + // Adds support for using ML-DSA private keys for generating service account tokens and adding support for ML-DSA signing algorithms for service account external JWT. + ServiceAccountTokenMLDSA featuregate.Feature = "ServiceAccountTokenMLDSA" + // owner: @jpbetz // // When enabled, the apiserver ignores changes to status fields in writes to the ServiceCIDR root resource. @@ -1950,6 +1956,11 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate {Version: version.MustParse("1.33"), Default: true, PreRelease: featuregate.Beta}, }, + ServiceAccountTokenMLDSA: { + {Version: version.MustParse("1.38"), Default: false, PreRelease: featuregate.Beta}, + {Version: version.MustParse("1.38"), Default: true, PreRelease: featuregate.Beta, MinCompatibilityVersion: version.MustParse("1.38")}, + }, + ServiceCIDRStatusFieldWiping: { {Version: version.MustParse("1.0"), Default: false, PreRelease: featuregate.GA}, {Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.Deprecated}, @@ -2614,6 +2625,8 @@ var defaultKubernetesFeatureGateDependencies = map[featuregate.Feature][]feature ServiceAccountNodeAudienceRestriction: {}, + ServiceAccountTokenMLDSA: {}, + ServiceCIDRStatusFieldWiping: {}, StaleControllerConsistencyDaemonSet: {featuregate.Feature(clientfeatures.AtomicFIFO)}, diff --git a/pkg/kubeapiserver/options/authentication_test.go b/pkg/kubeapiserver/options/authentication_test.go index 02196149d83..1e5277da1d1 100644 --- a/pkg/kubeapiserver/options/authentication_test.go +++ b/pkg/kubeapiserver/options/authentication_test.go @@ -595,7 +595,7 @@ kind: AuthenticationConfiguration anonymous: enabled: false `, - OIDCSigningAlgs: []string{"ES256", "ES384", "ES512", "PS256", "PS384", "PS512", "RS256", "RS384", "RS512"}, + OIDCSigningAlgs: []string{"ES256", "ES384", "ES512", "ML-DSA-44", "ML-DSA-65", "ML-DSA-87", "PS256", "PS384", "PS512", "RS256", "RS384", "RS512"}, }, }, { @@ -620,7 +620,7 @@ kind: AuthenticationConfiguration anonymous: enabled: true `, - OIDCSigningAlgs: []string{"ES256", "ES384", "ES512", "PS256", "PS384", "PS512", "RS256", "RS384", "RS512"}, + OIDCSigningAlgs: []string{"ES256", "ES384", "ES512", "ML-DSA-44", "ML-DSA-65", "ML-DSA-87", "PS256", "PS384", "PS512", "RS256", "RS384", "RS512"}, }, }, { @@ -690,7 +690,7 @@ anonymous: conditions: - path: "/livez" `, - OIDCSigningAlgs: []string{"ES256", "ES384", "ES512", "PS256", "PS384", "PS512", "RS256", "RS384", "RS512"}, + OIDCSigningAlgs: []string{"ES256", "ES384", "ES512", "ML-DSA-44", "ML-DSA-65", "ML-DSA-87", "PS256", "PS384", "PS512", "RS256", "RS384", "RS512"}, }, }, { @@ -752,7 +752,7 @@ jwt: claim: sub prefix: "" `, - OIDCSigningAlgs: []string{"ES256", "ES384", "ES512", "PS256", "PS384", "PS512", "RS256", "RS384", "RS512"}, + OIDCSigningAlgs: []string{"ES256", "ES384", "ES512", "ML-DSA-44", "ML-DSA-65", "ML-DSA-87", "PS256", "PS384", "PS512", "RS256", "RS384", "RS512"}, }, }, } @@ -1034,7 +1034,7 @@ jwt: claim: sub prefix: "" `, - OIDCSigningAlgs: []string{"ES256", "ES384", "ES512", "PS256", "PS384", "PS512", "RS256", "RS384", "RS512"}, + OIDCSigningAlgs: []string{"ES256", "ES384", "ES512", "ML-DSA-44", "ML-DSA-65", "ML-DSA-87", "PS256", "PS384", "PS512", "RS256", "RS384", "RS512"}, }, }, { diff --git a/pkg/serviceaccount/externaljwt/plugin/plugin.go b/pkg/serviceaccount/externaljwt/plugin/plugin.go index d25b505a462..90bb1c50001 100644 --- a/pkg/serviceaccount/externaljwt/plugin/plugin.go +++ b/pkg/serviceaccount/externaljwt/plugin/plugin.go @@ -30,7 +30,9 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" + utilfeature "k8s.io/apiserver/pkg/util/feature" externaljwtv1 "k8s.io/externaljwt/apis/v1" + "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/serviceaccount" externaljwtmetrics "k8s.io/kubernetes/pkg/serviceaccount/externaljwt/metrics" ) @@ -178,10 +180,15 @@ func (p *Plugin) validateJWTHeader(ctx context.Context, response *externaljwtv1. // - pkg/serviceaccount/externaljwt/plugin/plugin.go validateJWTHeader // - pkg/serviceaccount/jwt.go signerFromRSAPrivateKey // - pkg/serviceaccount/jwt.go signerFromECDSAPrivateKey + // - pkg/serviceaccount/jwt.go signerFromMLDSAPrivateKey // - pkg/serviceaccount/jwt.go AcceptableServiceAccountSignatureAlgorithms // - test/images/agnhost/openidmetadata/openidmetadata.go validate SupportedSigningAlgs case "RS256", "ES256", "ES384", "ES512": // OK + case "ML-DSA-44", "ML-DSA-65", "ML-DSA-87": + if !utilfeature.DefaultFeatureGate.Enabled(features.ServiceAccountTokenMLDSA) { + return fmt.Errorf("bad signing algorithm %q", header.Algorithm) + } default: return fmt.Errorf("bad signing algorithm %q", header.Algorithm) } diff --git a/pkg/serviceaccount/jwt.go b/pkg/serviceaccount/jwt.go index c51998f973e..5dfaf145177 100644 --- a/pkg/serviceaccount/jwt.go +++ b/pkg/serviceaccount/jwt.go @@ -21,6 +21,7 @@ import ( "crypto" "crypto/ecdsa" "crypto/elliptic" + "crypto/mldsa" "crypto/rsa" "crypto/x509" "encoding/base64" @@ -37,6 +38,8 @@ import ( "k8s.io/apiserver/pkg/audit" "k8s.io/apiserver/pkg/authentication/authenticator" apiserverserviceaccount "k8s.io/apiserver/pkg/authentication/serviceaccount" + utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/kubernetes/pkg/features" ) // ServiceAccountTokenGetter defines functions to retrieve a named service account and secret @@ -75,6 +78,14 @@ func JWTTokenGenerator(iss string, privateKey interface{}) (TokenGenerator, erro if err != nil { return nil, fmt.Errorf("could not generate signer for ECDSA keypair: %v", err) } + case *mldsa.PrivateKey: + if !utilfeature.DefaultFeatureGate.Enabled(features.ServiceAccountTokenMLDSA) { + return nil, fmt.Errorf("*mldsa.PrivateKey support is not enabled, must be *rsa.PrivateKey, *ecdsa.PrivateKey, or jose.OpaqueSigner") + } + signer, err = signerFromMLDSAPrivateKey(pk) + if err != nil { + return nil, fmt.Errorf("could not generate signer for ML-DSA keypair: %w", err) + } case jose.OpaqueSigner: signer, err = signerFromOpaqueSigner(pk) if err != nil { @@ -90,6 +101,52 @@ func JWTTokenGenerator(iss string, privateKey interface{}) (TokenGenerator, erro }, nil } +func signerFromMLDSAPrivateKey(pk *mldsa.PrivateKey) (jose.Signer, error) { + keyID, err := keyIDFromPublicKey(pk.PublicKey()) + if err != nil { + return nil, fmt.Errorf("getting key ID from public key: %w", err) + } + + alg := "" + var joseAlg jose.SignatureAlgorithm + + switch pk.PublicKey().Parameters() { + case mldsa.MLDSA44(): + alg = string(jose.MLDSA44) + joseAlg = jose.MLDSA44 + case mldsa.MLDSA65(): + alg = string(jose.MLDSA65) + joseAlg = jose.MLDSA65 + case mldsa.MLDSA87(): + alg = string(jose.MLDSA87) + joseAlg = jose.MLDSA87 + default: + return nil, fmt.Errorf("unknown ML-DSA key parameter: %v", pk.PublicKey().Parameters().String()) + } + + privateJWK := &jose.JSONWebKey{ + Algorithm: alg, + Key: pk, + KeyID: keyID, + Use: "sig", + } + + signer, err := jose.NewSigner( + jose.SigningKey{ + Algorithm: joseAlg, + Key: privateJWK, + }, + nil, + ) + + if err != nil { + return nil, fmt.Errorf("failed to create signer: %w", err) + } + + return signer, nil + +} + // keyIDFromPublicKey derives a key ID non-reversibly from a public key. // // The Key ID is field on a given on JWTs and JWKs that help relying parties @@ -124,6 +181,7 @@ func signerFromRSAPrivateKey(keyPair *rsa.PrivateKey) (jose.Signer, error) { // - pkg/serviceaccount/externaljwt/plugin/plugin.go validateJWTHeader // - pkg/serviceaccount/jwt.go signerFromRSAPrivateKey // - pkg/serviceaccount/jwt.go signerFromECDSAPrivateKey + // - pkg/serviceaccount/jwt.go signerFromMLDSAPrivateKey // - test/images/agnhost/openidmetadata/openidmetadata.go validate SupportedSigningAlgs // Wrap the RSA keypair in a JOSE JWK with the designated key ID. @@ -156,6 +214,7 @@ func signerFromECDSAPrivateKey(keyPair *ecdsa.PrivateKey) (jose.Signer, error) { // - pkg/serviceaccount/externaljwt/plugin/plugin.go validateJWTHeader // - pkg/serviceaccount/jwt.go signerFromRSAPrivateKey // - pkg/serviceaccount/jwt.go signerFromECDSAPrivateKey + // - pkg/serviceaccount/jwt.go signerFromMLDSAPrivateKey // - test/images/agnhost/openidmetadata/openidmetadata.go validate SupportedSigningAlgs switch keyPair.Curve { @@ -341,6 +400,7 @@ type Validator[PrivateClaims any] interface { // - pkg/serviceaccount/externaljwt/plugin/plugin.go validateJWTHeader // - pkg/serviceaccount/jwt.go signerFromRSAPrivateKey // - pkg/serviceaccount/jwt.go signerFromECDSAPrivateKey +// - pkg/serviceaccount/jwt.go signerFromMLDSAPrivateKey // - pkg/serviceaccount/jwt.go AcceptableServiceAccountSignatureAlgorithms // - test/images/agnhost/openidmetadata/openidmetadata.go validate SupportedSigningAlgs var AcceptableServiceAccountSignatureAlgorithms = []jose.SignatureAlgorithm{ @@ -348,6 +408,9 @@ var AcceptableServiceAccountSignatureAlgorithms = []jose.SignatureAlgorithm{ jose.ES384, jose.ES512, jose.RS256, + jose.MLDSA44, + jose.MLDSA65, + jose.MLDSA87, } func (j *jwtTokenAuthenticator[PrivateClaims]) AuthenticateToken(ctx context.Context, tokenData string) (*authenticator.Response, bool, error) { diff --git a/pkg/serviceaccount/jwt_test.go b/pkg/serviceaccount/jwt_test.go index 824ea3031fd..4621ec56a97 100644 --- a/pkg/serviceaccount/jwt_test.go +++ b/pkg/serviceaccount/jwt_test.go @@ -30,6 +30,7 @@ import ( v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apiserver/pkg/authentication/authenticator" + utilfeature "k8s.io/apiserver/pkg/util/feature" clientset "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/fake" typedv1core "k8s.io/client-go/kubernetes/typed/core/v1" @@ -37,7 +38,9 @@ import ( v1listers "k8s.io/client-go/listers/core/v1" "k8s.io/client-go/tools/cache" "k8s.io/client-go/util/keyutil" + featuregatetesting "k8s.io/component-base/featuregate/testing" serviceaccountcontroller "k8s.io/kubernetes/pkg/controller/serviceaccount" + "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/serviceaccount" ) @@ -120,6 +123,51 @@ X2i8uIp/C/ASqiIGUeeKQtX0/IR3qCXyThP/dbCiHrF3v1cuhBOHY8CLVg== // 3. URLSafe Base64-encoding the sha bytes const ecdsaKeyID = "SoABiieYuNx4UdqYvZRVeuC6SihxgLrhLy9peHMHpTc" +// openssl genpkey -algorithm ML-DSA-44 -provparam ml-dsa.output_formats=seed-only -out mldsa44-priv.pem +const mldsa44PrivateKey = `-----BEGIN PRIVATE KEY----- +MDQCAQAwCwYJYIZIAWUDBAMRBCKAIERz6p69gPaVMOBq4wlLM7B/ZKvGjkR3TPXR +vh+K2OoE +-----END PRIVATE KEY-----` + +// openssl pkey -in mldsa44-priv.pem -pubout -out mldsa44-public.pem +const mldsa44PublicKey = `-----BEGIN PUBLIC KEY----- +MIIFMjALBglghkgBZQMEAxEDggUhACd2RrSgGNuFoOXNXT3D7fZxUj+ks1cVspRx +a1e4xXDX9Ki/nJ6AfGoqC2idkuVFa/ZjTYgXxXvCdNgLHFGpHZ2EvUhEI3qoudiC +lRaT++szVlGeJePJZohgJ5sH8tl2bNg8ltKL0pDkA0HZvDxlTxgv/zBrlDx7merW +OqX+zLKXxHtOZEY4yM9dKfgZY/d5M5T7EVpPldR5QLzhvlwXmQl5i+sYrFtoJXRd +OTibV1AtfwgoHpL6CI0B7/pkmhKtMCL40lCh/j9bqilgrnfp1vI3d5Y2JHcvNLGQ +zGJhdbJo2ddZaUzQCha1uLm8vkapfSV5i4Co1l9grqpUAERRskZLggzOjciCcGtM +LysX+jHfTVxmQ2qVV0hbPo7IMdhVVVmK7Lc1mr1Q0Wq4nUBr/nd3HpO6/fcQcYK7 +04kXUmFSIIpGWEOi/vq5XSLBQpXpFtZBrY6o0MkqkXNjFTCcQ0H2x+N36bX6IolO +kCYlbbcuFLKplDIW0bx6YIJiKSiAj5eugLPZLPwwNEiafG1Pn6SaNvd+gXo7iBOW +HomUsh5auE6iWlIOvOepyDxWUECu1/pRxRqj1crTV7Kvz5q4oawVLzCHHg04pymW +ZIQPFgVQN/X4y8lHhX8/9x8+W2yS4uaSzWTL+YfgjLEjGH5EMbVi/KP4zwyUG1Sa +NE9pBcXN2e2EXEK/gE/zNOkAJMNi6N/5gocJ2KhT996h7XI/+Jnzc7IMTOoT9BHS +9VnwLjPPx/qQ6oOSn+Pgdr+kknQ1XJE3rPn5haEnTKwwe8t5VdqNb82pypX+VHhk +FK/tk79+5hnJzj+avD7dCVvbsjR0BafZv+fSZWH8wkmZYitBoN0wlc7F6n8bwKSV +VJKE5RD6xMIjs5n+DA0HVDfLYGM+1Qn05V7AaAP0FXuABEP+v65rAqzB3OpfIsow +EIrDbgloNz5nenrKEXFJYknONzn8M1eDBIej7RyWRgRtJOg1mNFP8WnbjBFqHfSA +mdOSWgVPqA6Wx6NZBLdLTGPK//MVayH2q+ixcemp5LFwYyqMIKQRf3F81uzH9gW9 +3u5vANCVtCmZT/PIYCCRGy4RzluJHs4tEv3AwgKXMsFhkEU3r0QujAkbqiWgX7rh +XDGMoEp/XVOaH+DAiEij3aDvmwfYeaXfOBLlhQgA8QEtv6Rl6Thaj2Cb3h2I2Kct +wmqga71FHDUcF2PFmv+g3LIgD7nY0SdYxe7kDIQ5PJgEdzlZRhRvEgeoPCpjdcP6 +6P0+QHyKEOktwtT1atWlblDq/lrfFvcRgJBxq+CydL4CaHp0Dr9rxF8Y0PGvTEQp +Z1987A7LJ2YcGIPsJZ28FSDUJ+rT2gNgv8Ja9+kqbW3pP2z4OVVlVog1TLU96Xbf +VZefwnDzYWn9I4y5QMG1gbmN7SRpcYfpiSbKYf5Lsm4sIT6+JmKQ0pTie5BPOhmZ +yc6j/aG3Aicq76O/BN75oRg+gSm7rPG6b5A5uTII9lI96ISXdIQ6MsHWEZbB/7ej +T3tZ/s0sRQxzPOA9YzbdRh3+g6Hz2LKafI8ob0VgroJ+O0Ev7SCaudHYK3fnkJ/X +YHAJEYoBiUxaoLpx3TeNRr8oiciSql7042vMi8cwdspnQ/bnyKrrJRtzMFvr4CCU +6BXhb3ZRWBopzR1HW7sJWr5nzjcF6WpPzERogNUmZIcMCrsp3h01VGNGhZD7qQWB +AqBiW3lpSxcS4S4TtrM/wCLB9cgtXXW7iYzJljI/VnJ7jN5NbJU= +-----END PUBLIC KEY-----` + +// Obtained by: +// +// 1. Serializing ecdsaPublicKey as DER +// 2. Taking the SHA256 of the DER bytes +// 3. URLSafe Base64-encoding the sha bytes +const mldsa44KeyID = "ujqYahCmg9pxpxdLGbGbhQyizN2vtd-lMTixkaKHggo" + func getPrivateKey(data string) interface{} { key, err := keyutil.ParsePrivateKeyPEM([]byte(data)) if err != nil { @@ -169,6 +217,12 @@ func TestTokenGenerateAndValidate(t *testing.T) { Namespace: "test", }, } + mldsa44Secret := &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-mldsa44-secret", + Namespace: "test", + }, + } // Generate the RSA token rsaGenerator, err := serviceaccount.JWTTokenGenerator(serviceaccount.LegacyIssuer, getPrivateKey(rsaPrivateKey)) @@ -237,6 +291,17 @@ func TestTokenGenerateAndValidate(t *testing.T) { t.Fatalf("error generating token: %v", err) } + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ServiceAccountTokenMLDSA, true) + + // Generate ML-DSA-44 token + mldsa44Token := generateMLDSA44Token(t, serviceaccount.LegacyIssuer, serviceAccount, mldsa44Secret) + + mldsa44Secret.Data = map[string][]byte{ + "token": []byte(mldsa44Token), + } + + checkJSONWebSignatureHasKeyID(t, mldsa44Token, mldsa44KeyID) + testCases := map[string]struct { Client clientset.Interface Keys []interface{} @@ -269,6 +334,13 @@ func TestTokenGenerateAndValidate(t *testing.T) { ExpectedErr: true, ExpectedOK: false, }, + "invalid keys (mldsa)": { + Token: mldsa44Token, + Client: nil, + Keys: []interface{}{getPublicKey(otherPublicKey), getPublicKey(rsaPublicKey)}, + ExpectedErr: true, + ExpectedOK: false, + }, "valid key (rsa)": { Token: rsaToken, Client: nil, @@ -306,6 +378,16 @@ func TestTokenGenerateAndValidate(t *testing.T) { ExpectedUserUID: expectedUserUID, ExpectedGroups: []string{"system:serviceaccounts", "system:serviceaccounts:test"}, }, + "valid key (mldsa)": { + Token: mldsa44Token, + Client: nil, + Keys: []interface{}{getPublicKey(mldsa44PublicKey)}, + ExpectedErr: false, + ExpectedOK: true, + ExpectedUserName: expectedUserName, + ExpectedUserUID: expectedUserUID, + ExpectedGroups: []string{"system:serviceaccounts", "system:serviceaccounts:test"}, + }, "rotated keys (rsa)": { Token: rsaToken, Client: nil, @@ -326,6 +408,16 @@ func TestTokenGenerateAndValidate(t *testing.T) { ExpectedUserUID: expectedUserUID, ExpectedGroups: []string{"system:serviceaccounts", "system:serviceaccounts:test"}, }, + "rotated keys (mldsa)": { + Token: mldsa44Token, + Client: nil, + Keys: []interface{}{getPublicKey(otherPublicKey), getPublicKey(rsaPublicKey), getPublicKey(mldsa44PublicKey)}, + ExpectedErr: false, + ExpectedOK: true, + ExpectedUserName: expectedUserName, + ExpectedUserUID: expectedUserUID, + ExpectedGroups: []string{"system:serviceaccounts", "system:serviceaccounts:test"}, + }, "valid lookup": { Token: rsaToken, Client: fake.NewSimpleClientset(serviceAccount, rsaSecret, ecdsaSecret), @@ -551,6 +643,25 @@ func generateECDSATokenWithMalformedIss(t *testing.T, serviceAccount *v1.Service return string(out) } +func generateMLDSA44Token(t *testing.T, iss string, serviceAccount *v1.ServiceAccount, mldsa44Secret *v1.Secret) string { + t.Helper() + + mldsaGenerator, err := serviceaccount.JWTTokenGenerator(iss, getPrivateKey(mldsa44PrivateKey)) + if err != nil { + t.Fatalf("error making generator: %v", err) + } + c, pc := serviceaccount.LegacyClaims(*serviceAccount, *mldsa44Secret) + mldsaToken, err := mldsaGenerator.GenerateToken(context.TODO(), c, pc) + if err != nil { + t.Fatalf("error generating token: %v", err) + } + if len(mldsaToken) == 0 { + t.Fatalf("no token generated") + } + + return mldsaToken +} + func TestStaticPublicKeysGetter(t *testing.T) { ecPrivate := getPrivateKey(ecdsaPrivateKey) ecPublic := getPublicKey(ecdsaPublicKey) diff --git a/pkg/serviceaccount/openidmetadata.go b/pkg/serviceaccount/openidmetadata.go index 784492e0c80..8c4cc71543c 100644 --- a/pkg/serviceaccount/openidmetadata.go +++ b/pkg/serviceaccount/openidmetadata.go @@ -21,6 +21,7 @@ import ( "crypto" "crypto/ecdsa" "crypto/elliptic" + "crypto/mldsa" "crypto/rsa" "encoding/json" "fmt" @@ -31,7 +32,9 @@ import ( "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/util/sets" + utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/klog/v2" + "k8s.io/kubernetes/pkg/features" ) const ( @@ -66,15 +69,18 @@ type openidConfig struct { func (p *openidConfigProvider) GetConfigJSON() ([]byte, int) { return p.config.Load().configJSON, p.pubKeyGetter.GetCacheAgeMaxSeconds() } + func (p *openidConfigProvider) GetKeysetJSON() ([]byte, int) { return p.config.Load().keysetJSON, p.pubKeyGetter.GetCacheAgeMaxSeconds() } + func (p *openidConfigProvider) Enqueue() { err := p.Update() if err != nil { klog.ErrorS(err, "failed to update openid config metadata") } } + func (p *openidConfigProvider) Update() error { pubKeys := []PublicKey{} @@ -326,6 +332,21 @@ func algorithmFromPublicKey(publicKey crypto.PublicKey) (jose.SignatureAlgorithm default: return "", fmt.Errorf("unknown private key curve, must be 256, 384, or 521") } + case *mldsa.PublicKey: + if !utilfeature.DefaultFeatureGate.Enabled(features.ServiceAccountTokenMLDSA) { + return "", fmt.Errorf("*mldsa.PrivateKey support is not enabled, must be *rsa.PrivateKey, *ecdsa.PrivateKey, or jose.OpaqueSigner") + } + + switch pk.Parameters() { + case mldsa.MLDSA44(): + return jose.MLDSA44, nil + case mldsa.MLDSA65(): + return jose.MLDSA65, nil + case mldsa.MLDSA87(): + return jose.MLDSA87, nil + default: + return "", fmt.Errorf("unknown ml-dsa key parameters, must be ML-DSA-44, ML-DSA-65, or ML-DSA-87") + } case jose.OpaqueSigner: return jose.SignatureAlgorithm(pk.Public().Algorithm), nil default: diff --git a/pkg/serviceaccount/openidmetadata_test.go b/pkg/serviceaccount/openidmetadata_test.go index 7021d1c3ef2..2f9fbfc8a6a 100644 --- a/pkg/serviceaccount/openidmetadata_test.go +++ b/pkg/serviceaccount/openidmetadata_test.go @@ -32,6 +32,9 @@ import ( jose "github.com/go-jose/go-jose/v4" "github.com/google/go-cmp/cmp" + utilfeature "k8s.io/apiserver/pkg/util/feature" + featuregatetesting "k8s.io/component-base/featuregate/testing" + "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/routes" "k8s.io/kubernetes/pkg/serviceaccount" ) @@ -97,6 +100,10 @@ func TestServeConfiguration(t *testing.T) { if err != nil { t.Fatal(err) } + mldsaKeysGetter, err := serviceaccount.StaticPublicKeysGetter([]interface{}{getPublicKey(mldsa44PublicKey)}) + if err != nil { + t.Fatal(err) + } keysGetter := &proxyKeyGetter{PublicKeysGetter: ecKeysGetter} s, jwksURI := setupServer(t, exampleIssuer, keysGetter) defer s.Close() @@ -115,6 +122,13 @@ func TestServeConfiguration(t *testing.T) { SubjectTypes: []string{"public"}, SigningAlgs: []string{"RS256"}, } + wantMLDSA := Configuration{ + Issuer: exampleIssuer, + JWKSURI: jwksURI, + ResponseTypes: []string{"id_token"}, + SubjectTypes: []string{"public"}, + SigningAlgs: []string{"ML-DSA-44"}, + } reqURL := s.URL + "/.well-known/openid-configuration" expectConfiguration(t, reqURL, wantEC) @@ -128,6 +142,16 @@ func TestServeConfiguration(t *testing.T) { listener.Enqueue() } expectConfiguration(t, reqURL, wantRSA) + + keysGetter.PublicKeysGetter = mldsaKeysGetter + + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ServiceAccountTokenMLDSA, true) + + // notify the metadata the keys changed, expected a modified response + for _, listener := range keysGetter.listeners { + listener.Enqueue() + } + expectConfiguration(t, reqURL, wantMLDSA) } func expectConfiguration(t *testing.T, reqURL string, want Configuration) { @@ -168,7 +192,7 @@ func TestServeKeys(t *testing.T) { t.Fatal(err) } - var serveKeysTests = []struct { + serveKeysTests := []struct { Name string Keys []interface{} WantKeys []jose.JSONWebKey @@ -349,6 +373,7 @@ func TestServeKeys(t *testing.T) { }) } } + func expectKeys(t *testing.T, reqURL string, wantKeys []jose.JSONWebKey) { resp, err := http.Get(reqURL) if err != nil { diff --git a/staging/src/k8s.io/apiextensions-apiserver/go.sum b/staging/src/k8s.io/apiextensions-apiserver/go.sum index 6b6d665a7a3..3de50355085 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/go.sum +++ b/staging/src/k8s.io/apiextensions-apiserver/go.sum @@ -51,7 +51,7 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.9.4 h1:xwjVlxEMR3S605oUlgBjKLTTeGFciYPGYCtF/35LKGo= github.com/fxamacker/cbor/v2 v2.9.4/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= -github.com/go-jose/go-jose/v4 v4.1.7/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= +github.com/go-jose/go-jose/v4 v4.1.9/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= diff --git a/staging/src/k8s.io/apiserver/go.mod b/staging/src/k8s.io/apiserver/go.mod index cde46218893..54b2ce34412 100644 --- a/staging/src/k8s.io/apiserver/go.mod +++ b/staging/src/k8s.io/apiserver/go.mod @@ -13,7 +13,7 @@ require ( github.com/coreos/go-systemd/v22 v22.7.0 github.com/emicklei/go-restful/v3 v3.14.0 github.com/fsnotify/fsnotify v1.9.0 - github.com/go-jose/go-jose/v4 v4.1.7 + github.com/go-jose/go-jose/v4 v4.1.9 github.com/go-logr/logr v1.4.4 github.com/go-openapi/jsonreference v1.0.2 github.com/google/gnostic-models v0.7.1 diff --git a/staging/src/k8s.io/apiserver/go.sum b/staging/src/k8s.io/apiserver/go.sum index 545b4c44e82..183e14adad8 100644 --- a/staging/src/k8s.io/apiserver/go.sum +++ b/staging/src/k8s.io/apiserver/go.sum @@ -52,8 +52,8 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.9.4 h1:xwjVlxEMR3S605oUlgBjKLTTeGFciYPGYCtF/35LKGo= github.com/fxamacker/cbor/v2 v2.9.4/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= -github.com/go-jose/go-jose/v4 v4.1.7 h1:Nac/98uT5LPkfbiFuYa8ktf62a2VxwQ4xbq1GK8pV5E= -github.com/go-jose/go-jose/v4 v4.1.7/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= +github.com/go-jose/go-jose/v4 v4.1.9 h1:pKfeQkig1NmgK4hJrZYXLEYURXfoJB61Larw0uIW4gY= +github.com/go-jose/go-jose/v4 v4.1.9/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= diff --git a/staging/src/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/oidc.go b/staging/src/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/oidc.go index a9f17667b48..0e25eaf6f14 100644 --- a/staging/src/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/oidc.go +++ b/staging/src/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/oidc.go @@ -255,16 +255,22 @@ func AllValidSigningAlgorithms() []string { } // allowlist of signing algorithms to ensure users don't mistakenly pass something goofy. +// +// IMPORTANT: The algorithms in this list must support at least the algorithms +// that we support for serviceaccount tokens (pkg/serviceaccount/jwt.go AcceptableServiceAccountSignatureAlgorithms). var allowedSigningAlgs = map[string]bool{ - oidc.RS256: true, - oidc.RS384: true, - oidc.RS512: true, - oidc.ES256: true, - oidc.ES384: true, - oidc.ES512: true, - oidc.PS256: true, - oidc.PS384: true, - oidc.PS512: true, + oidc.RS256: true, + oidc.RS384: true, + oidc.RS512: true, + oidc.ES256: true, + oidc.ES384: true, + oidc.ES512: true, + oidc.PS256: true, + oidc.PS384: true, + oidc.PS512: true, + string(jose.MLDSA44): true, + string(jose.MLDSA65): true, + string(jose.MLDSA87): true, } type AuthenticatorTokenWithHealthCheck interface { diff --git a/staging/src/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/oidc_test.go b/staging/src/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/oidc_test.go index e3f1cfa9d63..f5a0b679582 100644 --- a/staging/src/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/oidc_test.go +++ b/staging/src/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/oidc_test.go @@ -20,6 +20,7 @@ import ( "bytes" "context" "crypto" + "crypto/mldsa" "crypto/x509" "encoding/hex" "encoding/json" @@ -88,6 +89,38 @@ func loadECDSAPrivKey(t *testing.T, filepath string, alg jose.SignatureAlgorithm }) } +func loadMLDSAKey(t *testing.T, filepath string, alg jose.SignatureAlgorithm) *jose.JSONWebKey { + return loadKey(t, filepath, alg, func(b []byte) (interface{}, error) { + key, err := x509.ParsePKCS8PrivateKey(b) + if err != nil { + return nil, err + } + + mldsaKey, ok := key.(*mldsa.PrivateKey) + if !ok { + return nil, fmt.Errorf("loaded key is not an ML-DSA private key, got %T", key) + } + + return mldsaKey.PublicKey(), nil + }) +} + +func loadMLDSAPrivKey(t *testing.T, filepath string, alg jose.SignatureAlgorithm) *jose.JSONWebKey { + return loadKey(t, filepath, alg, func(b []byte) (interface{}, error) { + key, err := x509.ParsePKCS8PrivateKey(b) + if err != nil { + return nil, err + } + + mldsaKey, ok := key.(*mldsa.PrivateKey) + if !ok { + return nil, fmt.Errorf("loaded key is not an ML-DSA private key, got %T", key) + } + + return mldsaKey, nil + }) +} + func loadKey(t *testing.T, filepath string, alg jose.SignatureAlgorithm, unmarshal func([]byte) (interface{}, error)) *jose.JSONWebKey { data, err := os.ReadFile(filepath) if err != nil { @@ -2018,6 +2051,39 @@ func TestToken(t *testing.T) { Name: "jane", }, }, + { + name: "mldsa-44", + options: Options{ + JWTAuthenticator: apiserver.JWTAuthenticator{ + Issuer: apiserver.Issuer{ + URL: "https://auth.example.com", + Audiences: []string{"my-client"}, + }, + ClaimMappings: apiserver.ClaimMappings{ + Username: apiserver.PrefixedClaimOrExpression{ + Claim: "username", + Prefix: new(""), + }, + }, + }, + SupportedSigningAlgs: []string{"ML-DSA-44"}, + now: func() time.Time { return now }, + }, + signingKey: loadMLDSAPrivKey(t, "testdata/mldsa_2.pem", jose.MLDSA44), + pubKeys: []*jose.JSONWebKey{ + loadMLDSAKey(t, "testdata/mldsa_1.pem", jose.MLDSA44), + loadMLDSAKey(t, "testdata/mldsa_2.pem", jose.MLDSA44), + }, + claims: fmt.Sprintf(`{ + "iss": "https://auth.example.com", + "aud": "my-client", + "username": "jane", + "exp": %d + }`, valid.Unix()), + want: &user.DefaultInfo{ + Name: "jane", + }, + }, { name: "not-https", options: Options{ diff --git a/staging/src/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/testdata/gen.sh b/staging/src/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/testdata/gen.sh index d7b3df81677..ddf20146592 100755 --- a/staging/src/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/testdata/gen.sh +++ b/staging/src/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/testdata/gen.sh @@ -26,4 +26,8 @@ for N in $(seq 1 3); do ssh-keygen -t ecdsa -b 521 -f ecdsa_"$N".pem -N '' done +for N in $(seq 1 3); do + openssl genpkey -algorithm ML-DSA-44 -provparam ml-dsa.output_formats=seed-only -out mldsa_"$N".pem +done + rm ./*.pub diff --git a/staging/src/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/testdata/mldsa_1.pem b/staging/src/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/testdata/mldsa_1.pem new file mode 100644 index 00000000000..dc75eeea1aa --- /dev/null +++ b/staging/src/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/testdata/mldsa_1.pem @@ -0,0 +1,4 @@ +-----BEGIN PRIVATE KEY----- +MDQCAQAwCwYJYIZIAWUDBAMRBCKAILcgxkjA2tFxP8V/KWAYGWa9lJRHSsGD/ea7 +06Ho3ATv +-----END PRIVATE KEY----- diff --git a/staging/src/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/testdata/mldsa_2.pem b/staging/src/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/testdata/mldsa_2.pem new file mode 100644 index 00000000000..450693a6749 --- /dev/null +++ b/staging/src/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/testdata/mldsa_2.pem @@ -0,0 +1,4 @@ +-----BEGIN PRIVATE KEY----- +MDQCAQAwCwYJYIZIAWUDBAMRBCKAIJL22V98+3MTtacNF4D/fcu5yyOgpPjTr/C3 +Ue3eGfBH +-----END PRIVATE KEY----- diff --git a/staging/src/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/testdata/mldsa_3.pem b/staging/src/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/testdata/mldsa_3.pem new file mode 100644 index 00000000000..d6623852ab7 --- /dev/null +++ b/staging/src/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/testdata/mldsa_3.pem @@ -0,0 +1,4 @@ +-----BEGIN PRIVATE KEY----- +MDQCAQAwCwYJYIZIAWUDBAMRBCKAIInkcsgPS8SuzcIcPsJVbLs+z9nsa0KTDV6W +LWlyRijt +-----END PRIVATE KEY----- diff --git a/staging/src/k8s.io/cloud-provider/go.sum b/staging/src/k8s.io/cloud-provider/go.sum index 4a1847ec526..879e305f135 100644 --- a/staging/src/k8s.io/cloud-provider/go.sum +++ b/staging/src/k8s.io/cloud-provider/go.sum @@ -47,7 +47,7 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.9.4 h1:xwjVlxEMR3S605oUlgBjKLTTeGFciYPGYCtF/35LKGo= github.com/fxamacker/cbor/v2 v2.9.4/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= -github.com/go-jose/go-jose/v4 v4.1.7/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= +github.com/go-jose/go-jose/v4 v4.1.9/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= diff --git a/staging/src/k8s.io/cluster-bootstrap/go.mod b/staging/src/k8s.io/cluster-bootstrap/go.mod index e5bea343a7e..6b848f9006a 100644 --- a/staging/src/k8s.io/cluster-bootstrap/go.mod +++ b/staging/src/k8s.io/cluster-bootstrap/go.mod @@ -7,7 +7,7 @@ go 1.27.0 godebug default=go1.27 require ( - github.com/go-jose/go-jose/v4 v4.1.7 + github.com/go-jose/go-jose/v4 v4.1.9 github.com/stretchr/testify v1.12.1 k8s.io/api v0.0.0 k8s.io/apimachinery v0.0.0 diff --git a/staging/src/k8s.io/cluster-bootstrap/go.sum b/staging/src/k8s.io/cluster-bootstrap/go.sum index ea729eb213b..5ad7b6656a4 100644 --- a/staging/src/k8s.io/cluster-bootstrap/go.sum +++ b/staging/src/k8s.io/cluster-bootstrap/go.sum @@ -2,8 +2,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fxamacker/cbor/v2 v2.9.4 h1:xwjVlxEMR3S605oUlgBjKLTTeGFciYPGYCtF/35LKGo= github.com/fxamacker/cbor/v2 v2.9.4/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= -github.com/go-jose/go-jose/v4 v4.1.7 h1:Nac/98uT5LPkfbiFuYa8ktf62a2VxwQ4xbq1GK8pV5E= -github.com/go-jose/go-jose/v4 v4.1.7/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= +github.com/go-jose/go-jose/v4 v4.1.9 h1:pKfeQkig1NmgK4hJrZYXLEYURXfoJB61Larw0uIW4gY= +github.com/go-jose/go-jose/v4 v4.1.9/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-openapi/jsonpointer v1.0.2/go.mod h1:N0rI4MbNKD7xAFTiHtB+5Nhrg2j/wwYg7m7Fmb4/jk8= diff --git a/staging/src/k8s.io/controller-manager/go.sum b/staging/src/k8s.io/controller-manager/go.sum index 856fc6adfb4..5a6652b25cc 100644 --- a/staging/src/k8s.io/controller-manager/go.sum +++ b/staging/src/k8s.io/controller-manager/go.sum @@ -44,7 +44,7 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.9.4 h1:xwjVlxEMR3S605oUlgBjKLTTeGFciYPGYCtF/35LKGo= github.com/fxamacker/cbor/v2 v2.9.4/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= -github.com/go-jose/go-jose/v4 v4.1.7/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= +github.com/go-jose/go-jose/v4 v4.1.9/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= diff --git a/staging/src/k8s.io/dynamic-resource-allocation/go.sum b/staging/src/k8s.io/dynamic-resource-allocation/go.sum index 2c0b1c9bfc2..b7918cf193a 100644 --- a/staging/src/k8s.io/dynamic-resource-allocation/go.sum +++ b/staging/src/k8s.io/dynamic-resource-allocation/go.sum @@ -33,7 +33,7 @@ github.com/felixge/httpsnoop v1.1.0/go.mod h1:Zqxgdd+1Rkcz8euOqdr7lqgCRJztwr5hp9 github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.9.4 h1:xwjVlxEMR3S605oUlgBjKLTTeGFciYPGYCtF/35LKGo= github.com/fxamacker/cbor/v2 v2.9.4/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= -github.com/go-jose/go-jose/v4 v4.1.7/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= +github.com/go-jose/go-jose/v4 v4.1.9/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= diff --git a/staging/src/k8s.io/externaljwt/apis/v1/api.pb.go b/staging/src/k8s.io/externaljwt/apis/v1/api.pb.go index cae526b75cc..95807ce2a5d 100644 --- a/staging/src/k8s.io/externaljwt/apis/v1/api.pb.go +++ b/staging/src/k8s.io/externaljwt/apis/v1/api.pb.go @@ -106,7 +106,7 @@ type SignJWTResponse struct { // header must contain only alg, kid, typ claims. // typ must be “JWT”. // kid must be non-empty, <=1024 characters, and its corresponding public key should not be excluded from OIDC discovery. - // alg must be one of the algorithms supported by kube-apiserver (currently RS256, ES256, ES384, ES512). + // alg must be one of the algorithms supported by kube-apiserver (currently RS256, ES256, ES384, ES512, ML-DSA-44, ML-DSA-65, ML-DSA-87). // header cannot have any additional data that kube-apiserver does not recognize. // Already wrapped in URL-safe base64, exactly as it appears in the first segment of the JWT. Header string `protobuf:"bytes,1,opt,name=header,proto3" json:"header,omitempty"` diff --git a/staging/src/k8s.io/externaljwt/apis/v1/api.proto b/staging/src/k8s.io/externaljwt/apis/v1/api.proto index 8fd25884178..535f0e3195c 100644 --- a/staging/src/k8s.io/externaljwt/apis/v1/api.proto +++ b/staging/src/k8s.io/externaljwt/apis/v1/api.proto @@ -55,7 +55,7 @@ service ExternalJWTSigner { // header must contain only alg, kid, typ claims. // typ must be “JWT”. // kid must be non-empty, <=1024 characters, and its corresponding public key should not be excluded from OIDC discovery. - // alg must be one of the algorithms supported by kube-apiserver (currently RS256, ES256, ES384, ES512). + // alg must be one of the algorithms supported by kube-apiserver (currently RS256, ES256, ES384, ES512, ML-DSA-44, ML-DSA-65, ML-DSA-87). // header cannot have any additional data that kube-apiserver does not recognize. // Already wrapped in URL-safe base64, exactly as it appears in the first segment of the JWT. string header = 1; @@ -109,4 +109,4 @@ service ExternalJWTSigner { // // `max_token_expiration_seconds` must be at least 600s. int64 max_token_expiration_seconds = 1; - } \ No newline at end of file + } diff --git a/staging/src/k8s.io/kube-aggregator/go.sum b/staging/src/k8s.io/kube-aggregator/go.sum index f3f0474a099..786ce6b2e03 100644 --- a/staging/src/k8s.io/kube-aggregator/go.sum +++ b/staging/src/k8s.io/kube-aggregator/go.sum @@ -45,7 +45,7 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.9.4 h1:xwjVlxEMR3S605oUlgBjKLTTeGFciYPGYCtF/35LKGo= github.com/fxamacker/cbor/v2 v2.9.4/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= -github.com/go-jose/go-jose/v4 v4.1.7/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= +github.com/go-jose/go-jose/v4 v4.1.9/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= diff --git a/staging/src/k8s.io/pod-security-admission/go.sum b/staging/src/k8s.io/pod-security-admission/go.sum index 856fc6adfb4..5a6652b25cc 100644 --- a/staging/src/k8s.io/pod-security-admission/go.sum +++ b/staging/src/k8s.io/pod-security-admission/go.sum @@ -44,7 +44,7 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.9.4 h1:xwjVlxEMR3S605oUlgBjKLTTeGFciYPGYCtF/35LKGo= github.com/fxamacker/cbor/v2 v2.9.4/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= -github.com/go-jose/go-jose/v4 v4.1.7/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= +github.com/go-jose/go-jose/v4 v4.1.9/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= diff --git a/staging/src/k8s.io/sample-apiserver/go.sum b/staging/src/k8s.io/sample-apiserver/go.sum index 80e79419508..2b38d88aa1c 100644 --- a/staging/src/k8s.io/sample-apiserver/go.sum +++ b/staging/src/k8s.io/sample-apiserver/go.sum @@ -44,7 +44,7 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.9.4 h1:xwjVlxEMR3S605oUlgBjKLTTeGFciYPGYCtF/35LKGo= github.com/fxamacker/cbor/v2 v2.9.4/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= -github.com/go-jose/go-jose/v4 v4.1.7/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= +github.com/go-jose/go-jose/v4 v4.1.9/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= diff --git a/test/compatibility_lifecycle/reference/feature_list.md b/test/compatibility_lifecycle/reference/feature_list.md index e1574b4f920..e4281664092 100644 --- a/test/compatibility_lifecycle/reference/feature_list.md +++ b/test/compatibility_lifecycle/reference/feature_list.md @@ -197,6 +197,7 @@ | SecurityProfileOCI | | | 1.38– | | | | | [code](https://cs.k8s.io/?q=%5CbSecurityProfileOCI%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbSecurityProfileOCI%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) | | SeparateCacheWatchRPC | :ballot_box_with_check: 1.28+ | :closed_lock_with_key: 1.36+ | | 1.28–1.32 | | 1.33– | | [code](https://cs.k8s.io/?q=%5CbSeparateCacheWatchRPC%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbSeparateCacheWatchRPC%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) | | ServiceAccountNodeAudienceRestriction | :ballot_box_with_check: 1.33+ | | | 1.32– | | | | [code](https://cs.k8s.io/?q=%5CbServiceAccountNodeAudienceRestriction%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbServiceAccountNodeAudienceRestriction%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) | +| ServiceAccountTokenMLDSA | :ballot_box_with_check: 1.38+ | | | 1.38– | | | | [code](https://cs.k8s.io/?q=%5CbServiceAccountTokenMLDSA%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbServiceAccountTokenMLDSA%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) | | ServiceCIDRStatusFieldWiping | :ballot_box_with_check: 1.36+ | | | | 1.0–1.35 | 1.36– | | [code](https://cs.k8s.io/?q=%5CbServiceCIDRStatusFieldWiping%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbServiceCIDRStatusFieldWiping%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) | | ShardedListAndWatch | :ballot_box_with_check: 1.38+ | | 1.36–1.37 | 1.38– | | | | [code](https://cs.k8s.io/?q=%5CbShardedListAndWatch%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbShardedListAndWatch%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) | | SizeBasedListCostEstimate | :ballot_box_with_check: 1.34+ | | | 1.34– | | | | [code](https://cs.k8s.io/?q=%5CbSizeBasedListCostEstimate%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbSizeBasedListCostEstimate%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) | diff --git a/test/compatibility_lifecycle/reference/versioned_feature_list.yaml b/test/compatibility_lifecycle/reference/versioned_feature_list.yaml index 9f438ea0ec3..8ffd5892a69 100644 --- a/test/compatibility_lifecycle/reference/versioned_feature_list.yaml +++ b/test/compatibility_lifecycle/reference/versioned_feature_list.yaml @@ -1806,6 +1806,17 @@ lockToDefault: false preRelease: Beta version: "1.33" +- name: ServiceAccountTokenMLDSA + versionedSpecs: + - default: false + lockToDefault: false + preRelease: Beta + version: "1.38" + - default: true + lockToDefault: false + preRelease: Beta + version: "1.38" + minCompatibilityVersion: "1.38" - name: ServiceCIDRStatusFieldWiping versionedSpecs: - default: false diff --git a/test/images/agnhost/openidmetadata/openidmetadata.go b/test/images/agnhost/openidmetadata/openidmetadata.go index fa610a84103..176ae915f26 100644 --- a/test/images/agnhost/openidmetadata/openidmetadata.go +++ b/test/images/agnhost/openidmetadata/openidmetadata.go @@ -30,6 +30,7 @@ import ( "time" "github.com/coreos/go-oidc/v3/oidc" + "github.com/go-jose/go-jose/v4" "github.com/go-jose/go-jose/v4/jwt" "github.com/spf13/cobra" "golang.org/x/oauth2" @@ -128,12 +129,13 @@ func validate(ctx context.Context, raw string) error { // - pkg/serviceaccount/externaljwt/plugin/plugin.go validateJWTHeader // - pkg/serviceaccount/jwt.go signerFromRSAPrivateKey // - pkg/serviceaccount/jwt.go signerFromECDSAPrivateKey + // - pkg/serviceaccount/jwt.go signerFromMLDSAPrivateKey // - pkg/serviceaccount/jwt.go AcceptableServiceAccountSignatureAlgorithms // - test/images/agnhost/openidmetadata/openidmetadata.go validate SupportedSigningAlgs validTok, err := iss.Verifier(&oidc.Config{ ClientID: audience, - SupportedSigningAlgs: []string{oidc.RS256, oidc.ES256, oidc.ES384, oidc.ES512}, + SupportedSigningAlgs: []string{oidc.RS256, oidc.ES256, oidc.ES384, oidc.ES512, string(jose.MLDSA44), string(jose.MLDSA65), string(jose.MLDSA87)}, }).Verify(ctx, raw) if err != nil { return err diff --git a/vendor/github.com/go-jose/go-jose/v4/README.md b/vendor/github.com/go-jose/go-jose/v4/README.md index 55c5509176b..2cc047988f1 100644 --- a/vendor/github.com/go-jose/go-jose/v4/README.md +++ b/vendor/github.com/go-jose/go-jose/v4/README.md @@ -61,16 +61,19 @@ standard where possible. The Godoc reference has a list of constants. 1. Not supported in multi-recipient mode -| Signing / MAC | Algorithm identifier(s) | -|:------------------|:------------------------| -| RSASSA-PKCS#1v1.5 | RS256, RS384, RS512 | -| RSASSA-PSS | PS256, PS384, PS512 | -| HMAC | HS256, HS384, HS512 | -| ECDSA | ES256, ES384, ES512 | -| Ed25519 | EdDSA2 | +| Signing / MAC | Algorithm identifier(s) | +|:------------------|:--------------------------------------------| +| RSASSA-PKCS#1v1.5 | RS256, RS384, RS512 | +| RSASSA-PSS | PS256, PS384, PS512 | +| HMAC | HS256, HS384, HS512 | +| ECDSA | ES256, ES384, ES512 | +| Ed25519 | EdDSA2 | +| ML-DSA | ML-DSA-44, ML-DSA-65, ML-DSA-873 | 2. Only available in version 2 of the package +3. Requires Go 1.27 or later + | Content encryption | Algorithm identifier(s) | |:-------------------|:--------------------------------------------| | AES-CBC+HMAC | A128CBC-HS256, A192CBC-HS384, A256CBC-HS512 | @@ -87,15 +90,18 @@ library, and can be passed to corresponding functions such as `NewEncrypter` or `NewSigner`. Each of these keys can also be wrapped in a JWK if desired, which allows attaching a key id. -| Algorithm(s) | Corresponding types | -|:------------------|--------------------------------------------------------------------------------------------------------------------------------------| -| RSA | *[rsa.PublicKey](https://pkg.go.dev/crypto/rsa/#PublicKey), *[rsa.PrivateKey](https://pkg.go.dev/crypto/rsa/#PrivateKey) | -| ECDH, ECDSA | *[ecdsa.PublicKey](https://pkg.go.dev/crypto/ecdsa/#PublicKey), *[ecdsa.PrivateKey](https://pkg.go.dev/crypto/ecdsa/#PrivateKey) | -| EdDSA1 | [ed25519.PublicKey](https://pkg.go.dev/crypto/ed25519#PublicKey), [ed25519.PrivateKey](https://pkg.go.dev/crypto/ed25519#PrivateKey) | -| AES, HMAC | []byte | +| Algorithm(s) | Corresponding types | +|:-------------------|--------------------------------------------------------------------------------------------------------------------------------------| +| RSA | *[rsa.PublicKey](https://pkg.go.dev/crypto/rsa/#PublicKey), *[rsa.PrivateKey](https://pkg.go.dev/crypto/rsa/#PrivateKey) | +| ECDH, ECDSA | *[ecdsa.PublicKey](https://pkg.go.dev/crypto/ecdsa/#PublicKey), *[ecdsa.PrivateKey](https://pkg.go.dev/crypto/ecdsa/#PrivateKey) | +| EdDSA1 | [ed25519.PublicKey](https://pkg.go.dev/crypto/ed25519#PublicKey), [ed25519.PrivateKey](https://pkg.go.dev/crypto/ed25519#PrivateKey) | +| ML-DSA2 | *[mldsa.PublicKey](https://pkg.go.dev/crypto/mldsa#PublicKey), *[mldsa.PrivateKey](https://pkg.go.dev/crypto/mldsa#PrivateKey) | +| AES, HMAC | []byte | 1. Only available in version 2 or later of the package +2. Requires Go 1.27 or later + ## Examples [![godoc](https://pkg.go.dev/badge/github.com/go-jose/go-jose/v4.svg)](https://pkg.go.dev/github.com/go-jose/go-jose/v4) diff --git a/vendor/github.com/go-jose/go-jose/v4/doc.go b/vendor/github.com/go-jose/go-jose/v4/doc.go index 0ad40ca085f..6ea15ef6d39 100644 --- a/vendor/github.com/go-jose/go-jose/v4/doc.go +++ b/vendor/github.com/go-jose/go-jose/v4/doc.go @@ -21,5 +21,10 @@ the JSON Web Encryption and JSON Web Signature standards, with optional JSON Web Token support available in a sub-package. The library supports both the compact and JWS/JWE JSON Serialization formats, and has optional support for multiple recipients. + +ML-DSA (FIPS 204) signing and verification, and the AKP JSON Web Key type, are +defined by RFC 9964 and require Go 1.27 or later. On earlier toolchains the +ML-DSA algorithms are recognized but unusable, and AKP keys fail to parse with +ErrUnsupportedKeyType. */ package jose diff --git a/vendor/github.com/go-jose/go-jose/v4/jwk.go b/vendor/github.com/go-jose/go-jose/v4/jwk.go index 87acb2c5e1b..bbaa83f65b0 100644 --- a/vendor/github.com/go-jose/go-jose/v4/jwk.go +++ b/vendor/github.com/go-jose/go-jose/v4/jwk.go @@ -51,6 +51,11 @@ type rawJSONWebKey struct { Y *byteBuffer `json:"y,omitempty"` N *byteBuffer `json:"n,omitempty"` E *byteBuffer `json:"e,omitempty"` + // AKP (RFC 9964). "pub" is the encoded public key; "priv" is the 32-byte + // seed. An AKP key has no "crv" / "alg" identifies the parameter set, and is + // REQUIRED. + Pub *byteBuffer `json:"pub,omitempty"` + Priv *byteBuffer `json:"priv,omitempty"` // -- Following fields are only used for private keys -- // RSA uses D, P and Q, while ECDSA uses only D. Fields Dp, Dq, and Qi are // completely optional. Therefore for RSA/ECDSA, D != nil is a contract that @@ -122,7 +127,11 @@ func (k JSONWebKey) MarshalJSON() ([]byte, error) { case []byte: raw, err = fromSymmetricKey(key) default: - return nil, fmt.Errorf("go-jose/go-jose: unknown key type '%s'", reflect.TypeOf(key)) + var ok bool + raw, ok, err = mldsaRawJWK(k.Key) + if !ok { + return nil, fmt.Errorf("go-jose/go-jose: unknown key type '%s'", reflect.TypeOf(key)) + } } if err != nil { @@ -130,9 +139,19 @@ func (k JSONWebKey) MarshalJSON() ([]byte, error) { } raw.Kid = k.KeyID - raw.Alg = k.Algorithm raw.Use = k.Use + if raw.Kty == "AKP" { + // RFC 9964 requires "alg" on every AKP key, and mldsaRawJWK already + // derived it from the key's parameter set. Rather than let a caller's + // Algorithm silently overwrite it, treat a disagreement as an error. + if k.Algorithm != "" && k.Algorithm != raw.Alg { + return nil, fmt.Errorf("go-jose/go-jose: JWK algorithm %q does not match ML-DSA key parameter set %q", k.Algorithm, raw.Alg) + } + } else { + raw.Alg = k.Algorithm + } + for _, cert := range k.Certificates { raw.X5c = append(raw.X5c, base64.StdEncoding.EncodeToString(cert.Raw)) } @@ -177,7 +196,7 @@ func (k JSONWebKey) MarshalJSON() ([]byte, error) { // UnmarshalJSON reads a key from its JSON representation. // -// Returns ErrUnsupportedKeyType for unrecognized or unsupported "kty" header values. +// Returns ErrUnsupportedKeyType for unsupported key types or parameters. // // Directly unmarshaling a JWK is not safe for untrusted input. func (k *JSONWebKey) UnmarshalJSON(data []byte) error { @@ -197,7 +216,7 @@ func (k *JSONWebKey) unmarshalJSON(data []byte, allowPrivate bool) (err error) { if !allowPrivate && (raw.K != nil || raw.D != nil || raw.P != nil || raw.Q != nil || - raw.Dp != nil || raw.Dq != nil || raw.Qi != nil) { + raw.Dp != nil || raw.Dq != nil || raw.Qi != nil || raw.Priv != nil) { return errors.New("go-jose/go-jose: JWK contains private parameters") } @@ -273,6 +292,14 @@ func (k *JSONWebKey) unmarshalJSON(data []byte, allowPrivate bool) (err error) { keyPub = key } } + case "AKP": + key, err = mldsaParseJWK(&raw, certPub) + if err != nil { + return err + } + // mldsaParseJWK compared certPub using (*mldsa.PublicKey).Equal, so the + // generic comparison below only needs to confirm that a leaf key exists. + keyPub = certPub case "": // kty MUST be present return fmt.Errorf("go-jose/go-jose: missing json web key type") @@ -446,7 +473,11 @@ func (k *JSONWebKey) Thumbprint(hash crypto.Hash) ([]byte, error) { case OpaqueSigner: return key.Public().Thumbprint(hash) default: - return nil, fmt.Errorf("go-jose/go-jose: unknown key type '%s'", reflect.TypeOf(key)) + var ok bool + input, ok, err = mldsaThumbprintInput(k.Key) + if !ok { + return nil, fmt.Errorf("go-jose/go-jose: unknown key type '%s'", reflect.TypeOf(key)) + } } if err != nil { @@ -464,7 +495,7 @@ func (k *JSONWebKey) IsPublic() bool { case *ecdsa.PublicKey, *rsa.PublicKey, ed25519.PublicKey: return true default: - return false + return mldsaIsPublic(k.Key) } } @@ -482,7 +513,11 @@ func (k *JSONWebKey) Public() JSONWebKey { case ed25519.PrivateKey: ret.Key = key.Public() default: - return JSONWebKey{} // returning invalid key + pub, ok := mldsaPublicOf(k.Key) + if !ok { + return JSONWebKey{} // returning invalid key + } + ret.Key = pub } return ret } @@ -518,7 +553,7 @@ func (k *JSONWebKey) Valid() bool { return false } default: - return false + return mldsaValid(k.Key) } return true } @@ -542,7 +577,7 @@ func rsaPublicKey(N, E *byteBuffer) (*rsa.PublicKey, error) { n := N.bigInt() if n.BitLen() > MaxRSAModulusBits { - return nil, fmt.Errorf("go-jose/go-jose: invalid RSA key, modulus too large") + return nil, ErrUnsupportedKeyType } e := new(big.Int).SetBytes(E.bytes()) @@ -591,7 +626,7 @@ func (key rawJSONWebKey) ecPublicKey() (*ecdsa.PublicKey, error) { case "P-521": curve = elliptic.P521() default: - return nil, fmt.Errorf("go-jose/go-jose: unsupported elliptic curve '%s'", key.Crv) + return nil, ErrUnsupportedKeyType } if key.X == nil || key.Y == nil { @@ -798,7 +833,7 @@ func (key rawJSONWebKey) ecPrivateKey() (*ecdsa.PrivateKey, error) { case "P-521": curve = elliptic.P521() default: - return nil, fmt.Errorf("go-jose/go-jose: unsupported elliptic curve '%s'", key.Crv) + return nil, ErrUnsupportedKeyType } var missing []string diff --git a/vendor/github.com/go-jose/go-jose/v4/mldsa.go b/vendor/github.com/go-jose/go-jose/v4/mldsa.go new file mode 100644 index 00000000000..2b3472e4ca0 --- /dev/null +++ b/vendor/github.com/go-jose/go-jose/v4/mldsa.go @@ -0,0 +1,318 @@ +//go:build go1.27 + +/*- + * Copyright 2026 The Go JOSE Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jose + +import ( + "bytes" + "crypto/mldsa" + "errors" + "fmt" +) + +// mldsaParamsFor maps a JOSE signature algorithm to its ML-DSA parameter set. +// +// See RFC 9964, which registers ML-DSA-44, ML-DSA-65 and ML-DSA-87 as JWS "alg" +// values for the FIPS 204 parameter sets of the same names. +func mldsaParamsFor(alg SignatureAlgorithm) (mldsa.Parameters, bool) { + switch alg { + case MLDSA44: + return mldsa.MLDSA44(), true + case MLDSA65: + return mldsa.MLDSA65(), true + case MLDSA87: + return mldsa.MLDSA87(), true + default: + return mldsa.Parameters{}, false + } +} + +// mldsaAlgFor maps an ML-DSA parameter set to its JOSE signature algorithm. An +// AKP JWK carries no "crv", so the parameter set is the only thing that +// identifies which algorithm a key belongs to. +func mldsaAlgFor(params mldsa.Parameters) (SignatureAlgorithm, bool) { + switch params { + case mldsa.MLDSA44(): + return MLDSA44, true + case mldsa.MLDSA65(): + return MLDSA65, true + case mldsa.MLDSA87(): + return MLDSA87, true + default: + return "", false + } +} + +// mldsaIsPublic reports whether key is an ML-DSA public key. +func mldsaIsPublic(key interface{}) bool { + _, ok := key.(*mldsa.PublicKey) + return ok +} + +// mldsaValid reports whether key is an initialized ML-DSA key. It backs +// JSONWebKey.Valid, which is how a caller asks this question. mldsaVerifier +// rejects a nil or zero-value key and mldsaSigner rejects a nil key. The other +// hooks treat an uninitialized key as a programmer error, and crypto/mldsa +// panics on one. +func mldsaValid(key interface{}) bool { + switch k := key.(type) { + case *mldsa.PublicKey: + return k != nil && *k != (mldsa.PublicKey{}) + case *mldsa.PrivateKey: + return k != nil && *k != (mldsa.PrivateKey{}) + default: + return false + } +} + +// mldsaPublicOf returns the public half of an ML-DSA private key. +func mldsaPublicOf(key interface{}) (interface{}, bool) { + if k, ok := key.(*mldsa.PrivateKey); ok { + return k.PublicKey(), true + } + return nil, false +} + +// mldsaPrivateKeySigner signs payloads with an ML-DSA private key. The algorithm +// is resolved from the key's parameter set once at construction and cached. +type mldsaPrivateKeySigner struct { + privateKey *mldsa.PrivateKey + alg SignatureAlgorithm +} + +// mldsaPublicKeyVerifier verifies payloads with an ML-DSA public key. +type mldsaPublicKeyVerifier struct { + publicKey *mldsa.PublicKey + alg SignatureAlgorithm +} + +// mldsaSigner creates a recipientSigInfo for an ML-DSA private key. The second +// return value reports whether key was an ML-DSA key at all. +func mldsaSigner(sigAlg SignatureAlgorithm, key interface{}) (recipientSigInfo, bool, error) { + privateKey, ok := key.(*mldsa.PrivateKey) + if !ok { + return recipientSigInfo{}, false, nil + } + if privateKey == nil { + return recipientSigInfo{}, true, errors.New("invalid private key") + } + + alg, ok := mldsaAlgFor(privateKey.PublicKey().Parameters()) + if !ok || alg != sigAlg { + return recipientSigInfo{}, true, ErrUnsupportedAlgorithm + } + + return recipientSigInfo{ + sigAlg: sigAlg, + publicKey: staticPublicKey(&JSONWebKey{ + Key: privateKey.PublicKey(), + }), + signer: &mldsaPrivateKeySigner{ + privateKey: privateKey, + alg: alg, + }, + }, true, nil +} + +// mldsaVerifier creates a payloadVerifier for an ML-DSA public key. +func mldsaVerifier(key interface{}) (payloadVerifier, bool, error) { + publicKey, ok := key.(*mldsa.PublicKey) + if !ok { + return nil, false, nil + } + if publicKey == nil || *publicKey == (mldsa.PublicKey{}) { + return nil, true, errInvalidVerificationKey + } + + alg, ok := mldsaAlgFor(publicKey.Parameters()) + if !ok { + return nil, true, ErrUnsupportedKeyType + } + + return &mldsaPublicKeyVerifier{ + publicKey: publicKey, + alg: alg, + }, true, nil +} + +// mldsaRawJWK converts an ML-DSA key to its AKP JWK representation. The "alg" +// member is derived from the key's own parameter set, because RFC 9964 requires +// it on every AKP key and it is the only member identifying the parameter set. +func mldsaRawJWK(key interface{}) (*rawJSONWebKey, bool, error) { + var ( + pub *mldsa.PublicKey + priv *mldsa.PrivateKey + ) + + switch k := key.(type) { + case *mldsa.PublicKey: + pub = k + case *mldsa.PrivateKey: + priv, pub = k, k.PublicKey() + default: + return nil, false, nil + } + + alg, ok := mldsaAlgFor(pub.Parameters()) + if !ok { + return nil, true, ErrUnsupportedKeyType + } + + raw := &rawJSONWebKey{ + Kty: "AKP", + Alg: string(alg), + Pub: newBuffer(pub.Bytes()), + } + if priv != nil { + // RFC 9964: priv MUST be the seed, and MUST be 32 bytes. Bytes() returns + // exactly that. + raw.Priv = newBuffer(priv.Bytes()) + } + + return raw, true, nil +} + +// mldsaParseJWK builds an ML-DSA key from an AKP JWK. +// +// RFC 9964 makes "alg" REQUIRED for AKP keys: unlike EC and OKP there is no +// "crv", so "alg" is the only member identifying the parameter set. A JWK +// without a recognized "alg" is therefore not merely unusual but unusable, and +// is rejected with ErrUnsupportedKeyType. +// +// A parsed key can never carry an alg/material disagreement: params is derived +// from raw.Alg below, and mldsa.NewPrivateKey/NewPublicKey both reject key +// material whose length disagrees with those params. +// +// When certPub is non-nil it is checked against the key's public half here, +// using (*mldsa.PublicKey).Equal, so that the caller does not have to compare +// unexported crypto/mldsa internals with reflect.DeepEqual. +func mldsaParseJWK(raw *rawJSONWebKey, certPub interface{}) (interface{}, error) { + params, ok := mldsaParamsFor(SignatureAlgorithm(raw.Alg)) + if !ok { + return nil, ErrUnsupportedKeyType + } + + // RFC 9964 makes "pub" REQUIRED on every AKP JWK. + if raw.Pub == nil { + return nil, ErrUnsupportedKeyType + } + + var ( + key interface{} + pub *mldsa.PublicKey + ) + + if raw.Priv != nil { + // RFC 9964: priv MUST be the seed, and MUST be 32 bytes. NewPrivateKey + // enforces the length. + privateKey, err := mldsa.NewPrivateKey(params, raw.Priv.bytes()) + if err != nil { + return nil, fmt.Errorf("go-jose/go-jose: invalid ML-DSA private key: %w", err) + } + pub = privateKey.PublicKey() + if !bytes.Equal(raw.Pub.bytes(), pub.Bytes()) { + return nil, errors.New("go-jose/go-jose: invalid ML-DSA key, pub does not match the public key derived from priv") + } + key = privateKey + } else { + // NewPublicKey rejects an encoding whose length disagrees with params, + // which is what catches an alg/pub mismatch. + publicKey, err := mldsa.NewPublicKey(params, raw.Pub.bytes()) + if err != nil { + return nil, fmt.Errorf("go-jose/go-jose: invalid ML-DSA public key: %w", err) + } + pub = publicKey + key = publicKey + } + + if certPub != nil && !pub.Equal(certPub) { + return nil, errors.New("go-jose/go-jose: invalid JWK, public keys in key and x5c fields do not match") + } + + return key, nil +} + +func (ctx mldsaPrivateKeySigner) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) { + if alg != ctx.alg { + return Signature{}, ErrUnsupportedAlgorithm + } + + // RFC 9964 specifies empty context string and doesn't specify pre-hashing. + // Passing a `nil` crypto.SignerOpts gets us that. + sig, err := ctx.privateKey.Sign(nil, payload, nil) + if err != nil { + return Signature{}, err + } + + return Signature{ + Signature: sig, + protected: &rawHeader{}, + }, nil +} + +func (ctx mldsaPublicKeyVerifier) verifyPayload(payload []byte, signature []byte, alg SignatureAlgorithm) error { + // Reject a header algorithm that disagrees with the key's parameter set, so + // an ML-DSA-44 key can never be used under an ML-DSA-87 header. + if alg != ctx.alg { + return ErrUnsupportedAlgorithm + } + + // A nil Options means an empty context string, matching RFC 9964. + if err := mldsa.Verify(ctx.publicKey, payload, signature, nil); err != nil { + return fmt.Errorf("go-jose/go-jose: %s signature failed to verify: %w", ctx.alg, err) + } + + return nil +} + +// mldsaThumbprintTemplate is the JWK Thumbprint input for AKP keys. RFC 9964 +// gives the required members as "alg", "kty" and "pub"; RFC 7638 requires them +// in lexicographic order with no whitespace. Note that AKP is the only key type +// in this package whose thumbprint includes "alg". The verbs take the algorithm +// name and the unpadded base64url encoding of the public key's FIPS 204 encoding. +const mldsaThumbprintTemplate = `{"alg":"%s","kty":"AKP","pub":"%s"}` + +// mldsaThumbprintInput builds the JWK Thumbprint input for an ML-DSA key. A +// private key thumbprints as its public half. +// +// key is the value of JSONWebKey.Key and may be of any type; only +// *mldsa.PublicKey and *mldsa.PrivateKey are ML-DSA keys. +// +// The second return value reports whether key was an ML-DSA key at all. When it +// is false the string is empty and the error is nil, leaving the caller to +// report the unknown key type. When it is true the error is non-nil if the key's +// parameter set has no JOSE algorithm. +func mldsaThumbprintInput(key interface{}) (string, bool, error) { + var pub *mldsa.PublicKey + + switch k := key.(type) { + case *mldsa.PublicKey: + pub = k + case *mldsa.PrivateKey: + pub = k.PublicKey() + default: + return "", false, nil + } + + alg, ok := mldsaAlgFor(pub.Parameters()) + if !ok { + return "", true, ErrUnsupportedKeyType + } + + return fmt.Sprintf(mldsaThumbprintTemplate, alg, newBuffer(pub.Bytes()).base64()), true, nil +} diff --git a/vendor/github.com/go-jose/go-jose/v4/mldsa_stub.go b/vendor/github.com/go-jose/go-jose/v4/mldsa_stub.go new file mode 100644 index 00000000000..6eb9f91a0d8 --- /dev/null +++ b/vendor/github.com/go-jose/go-jose/v4/mldsa_stub.go @@ -0,0 +1,45 @@ +//go:build !go1.27 + +/*- + * Copyright 2026 The Go JOSE Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// This file provides the ML-DSA seam for toolchains without crypto/mldsa, which +// was added in Go 1.27. Every hook reports that it did not recognize the key, so +// callers fall through to their existing unsupported-key/algorithm error. +// +// Each identifier here must have a counterpart in mldsa.go. + +package jose + +func mldsaIsPublic(_ interface{}) bool { return false } + +func mldsaValid(_ interface{}) bool { return false } + +func mldsaPublicOf(_ interface{}) (interface{}, bool) { return nil, false } + +func mldsaSigner(_ SignatureAlgorithm, _ interface{}) (recipientSigInfo, bool, error) { + return recipientSigInfo{}, false, nil +} + +func mldsaVerifier(_ interface{}) (payloadVerifier, bool, error) { return nil, false, nil } + +func mldsaRawJWK(_ interface{}) (*rawJSONWebKey, bool, error) { return nil, false, nil } + +func mldsaParseJWK(_ *rawJSONWebKey, _ interface{}) (interface{}, error) { + return nil, ErrUnsupportedKeyType +} + +func mldsaThumbprintInput(_ interface{}) (string, bool, error) { return "", false, nil } diff --git a/vendor/github.com/go-jose/go-jose/v4/opaque.go b/vendor/github.com/go-jose/go-jose/v4/opaque.go index 91a42fc1dca..10998618415 100644 --- a/vendor/github.com/go-jose/go-jose/v4/opaque.go +++ b/vendor/github.com/go-jose/go-jose/v4/opaque.go @@ -16,6 +16,12 @@ package jose +import ( + "crypto/ecdsa" + "crypto/ed25519" + "crypto/rsa" +) + // OpaqueSigner is an interface that supports signing payloads with opaque // private key(s). Private key operations performed by implementers may, for // example, occur in a hardware module. An OpaqueSigner may rotate signing keys @@ -46,8 +52,7 @@ func newOpaqueSigner(alg SignatureAlgorithm, signer OpaqueSigner) (recipientSigI return recipientSigInfo{}, ErrUnsupportedAlgorithm } - pk := signer.Public() - if pk != nil && !pk.IsPublic() { + if pk := signer.Public(); pk != nil && isKnownPrivateKeyType(pk.Key) { return recipientSigInfo{}, ErrNotPublic } @@ -60,6 +65,19 @@ func newOpaqueSigner(alg SignatureAlgorithm, signer OpaqueSigner) (recipientSigI }, nil } +// isKnownPrivateKeyType reports whether key is a private key of one of the +// supported signing algorithms. Other key types are allowed, since an +// OpaqueSigner may use a key type that go-jose does not know about. +func isKnownPrivateKeyType(key any) bool { + switch key.(type) { + case *ecdsa.PrivateKey, *rsa.PrivateKey, ed25519.PrivateKey: + return true + default: + _, ok := mldsaPublicOf(key) + return ok + } +} + func (o *opaqueSigner) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) { out, err := o.signer.SignPayload(payload, alg) if err != nil { diff --git a/vendor/github.com/go-jose/go-jose/v4/shared.go b/vendor/github.com/go-jose/go-jose/v4/shared.go index 7303615a9f0..590f6cbe6ae 100644 --- a/vendor/github.com/go-jose/go-jose/v4/shared.go +++ b/vendor/github.com/go-jose/go-jose/v4/shared.go @@ -53,11 +53,8 @@ var ( // algorithm that is not yet implemented. ErrUnsupportedAlgorithm = errors.New("go-jose/go-jose: unknown/unsupported algorithm") - // ErrUnsupportedKeyType indicates that the given key type/format is not - // supported. This occurs when trying to instantiate an encrypter and passing - // it a key of an unrecognized type or with unsupported parameters, such as - // an RSA private key with more than two primes. - ErrUnsupportedKeyType = errors.New("go-jose/go-jose: unsupported key type/format") + // ErrUnsupportedKeyType indicates that the given key is not supported. + ErrUnsupportedKeyType = errors.New("go-jose/go-jose: unsupported key type, format, curve, or RSA modulus size") // ErrInvalidKeySize indicates that the given key is not the correct size // for the selected algorithm. This can occur, for example, when trying to @@ -76,9 +73,6 @@ var ( // ErrMissingX5cHeader indicates that the JWT header is missing x5c headers. ErrMissingX5cHeader = errors.New("go-jose/go-jose: no x5c header present in message") - // ErrUnsupportedEllipticCurve indicates unsupported or unknown elliptic curve has been found. - ErrUnsupportedEllipticCurve = errors.New("go-jose/go-jose: unsupported/unknown elliptic curve") - // ErrUnsupportedCriticalHeader is returned when a header is marked critical but not supported by go-jose. ErrUnsupportedCriticalHeader = errors.New("go-jose/go-jose: unsupported critical header") @@ -113,19 +107,22 @@ const ( // Signature algorithms const ( - EdDSA = SignatureAlgorithm("EdDSA") - HS256 = SignatureAlgorithm("HS256") // HMAC using SHA-256 - HS384 = SignatureAlgorithm("HS384") // HMAC using SHA-384 - HS512 = SignatureAlgorithm("HS512") // HMAC using SHA-512 - RS256 = SignatureAlgorithm("RS256") // RSASSA-PKCS-v1.5 using SHA-256 - RS384 = SignatureAlgorithm("RS384") // RSASSA-PKCS-v1.5 using SHA-384 - RS512 = SignatureAlgorithm("RS512") // RSASSA-PKCS-v1.5 using SHA-512 - ES256 = SignatureAlgorithm("ES256") // ECDSA using P-256 and SHA-256 - ES384 = SignatureAlgorithm("ES384") // ECDSA using P-384 and SHA-384 - ES512 = SignatureAlgorithm("ES512") // ECDSA using P-521 and SHA-512 - PS256 = SignatureAlgorithm("PS256") // RSASSA-PSS using SHA256 and MGF1-SHA256 - PS384 = SignatureAlgorithm("PS384") // RSASSA-PSS using SHA384 and MGF1-SHA384 - PS512 = SignatureAlgorithm("PS512") // RSASSA-PSS using SHA512 and MGF1-SHA512 + EdDSA = SignatureAlgorithm("EdDSA") + HS256 = SignatureAlgorithm("HS256") // HMAC using SHA-256 + HS384 = SignatureAlgorithm("HS384") // HMAC using SHA-384 + HS512 = SignatureAlgorithm("HS512") // HMAC using SHA-512 + RS256 = SignatureAlgorithm("RS256") // RSASSA-PKCS-v1.5 using SHA-256 + RS384 = SignatureAlgorithm("RS384") // RSASSA-PKCS-v1.5 using SHA-384 + RS512 = SignatureAlgorithm("RS512") // RSASSA-PKCS-v1.5 using SHA-512 + ES256 = SignatureAlgorithm("ES256") // ECDSA using P-256 and SHA-256 + ES384 = SignatureAlgorithm("ES384") // ECDSA using P-384 and SHA-384 + ES512 = SignatureAlgorithm("ES512") // ECDSA using P-521 and SHA-512 + PS256 = SignatureAlgorithm("PS256") // RSASSA-PSS using SHA256 and MGF1-SHA256 + PS384 = SignatureAlgorithm("PS384") // RSASSA-PSS using SHA384 and MGF1-SHA384 + PS512 = SignatureAlgorithm("PS512") // RSASSA-PSS using SHA512 and MGF1-SHA512 + MLDSA44 = SignatureAlgorithm("ML-DSA-44") // ML-DSA-44 (FIPS 204, RFC 9964), requires Go 1.27 or later + MLDSA65 = SignatureAlgorithm("ML-DSA-65") // ML-DSA-65 (FIPS 204, RFC 9964), requires Go 1.27 or later + MLDSA87 = SignatureAlgorithm("ML-DSA-87") // ML-DSA-87 (FIPS 204, RFC 9964), requires Go 1.27 or later ) // Content encryption algorithms @@ -527,7 +524,7 @@ func curveName(crv elliptic.Curve) (string, error) { case elliptic.P521(): return "P-521", nil default: - return "", ErrUnsupportedEllipticCurve + return "", ErrUnsupportedKeyType } } diff --git a/vendor/github.com/go-jose/go-jose/v4/signing.go b/vendor/github.com/go-jose/go-jose/v4/signing.go index 008cd4a2709..832879c2589 100644 --- a/vendor/github.com/go-jose/go-jose/v4/signing.go +++ b/vendor/github.com/go-jose/go-jose/v4/signing.go @@ -43,6 +43,7 @@ type Signer interface { // // Key must have one of these types: // - ed25519.PrivateKey +// - *mldsa.PrivateKey (requires Go 1.27 or later) // - *ecdsa.PrivateKey // - *rsa.PrivateKey // - *JSONWebKey @@ -224,6 +225,9 @@ func newVerifier(verificationKey interface{}) (payloadVerifier, error) { case OpaqueVerifier: return &opaqueVerifier{verifier: verificationKey}, nil default: + if verifier, ok, err := mldsaVerifier(verificationKey); ok { + return verifier, err + } return nil, ErrUnsupportedKeyType } } @@ -255,6 +259,9 @@ func makeJWSRecipient(alg SignatureAlgorithm, signingKey interface{}) (recipient case OpaqueSigner: return newOpaqueSigner(alg, signingKey) default: + if recipient, ok, err := mldsaSigner(alg, signingKey); ok { + return recipient, err + } return recipientSigInfo{}, ErrUnsupportedKeyType } } @@ -300,6 +307,12 @@ func (ctx *genericSigner) Sign(payload []byte) (*JSONWebSignature, error) { // // See https://github.com/square/go-jose/issues/157 for more context. if ctx.embedJWK { + // MarshalJSON can fail for a semantically inconsistent key (an AKP + // key whose Algorithm contradicts its parameter set). Surface that + // as an error rather than letting mustSerializeJSON panic below. + if _, err := recipientPubKey.MarshalJSON(); err != nil { + return nil, err + } protected[headerJWK] = recipientPubKey } else { keyID := recipientPubKey.KeyID @@ -381,6 +394,7 @@ func (ctx *genericSigner) Options() SignerOptions { // // The verificationKey argument must have one of these types: // - ed25519.PublicKey +// - *mldsa.PublicKey (requires Go 1.27 or later) // - *ecdsa.PublicKey // - *rsa.PublicKey // - *JSONWebKey diff --git a/vendor/modules.txt b/vendor/modules.txt index e8e5185873a..5929aba660d 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -182,7 +182,7 @@ github.com/fxamacker/cbor/v2 # github.com/go-errors/errors v1.5.1 ## explicit; go 1.14 github.com/go-errors/errors -# github.com/go-jose/go-jose/v4 v4.1.7 +# github.com/go-jose/go-jose/v4 v4.1.9 ## explicit; go 1.24.0 github.com/go-jose/go-jose/v4 github.com/go-jose/go-jose/v4/cipher