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
[](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