diff --git a/pkg/common/bundleutil/marshal.go b/pkg/common/bundleutil/marshal.go index 57adaa53de..e8428d35bf 100644 --- a/pkg/common/bundleutil/marshal.go +++ b/pkg/common/bundleutil/marshal.go @@ -14,6 +14,7 @@ type marshalConfig struct { sequenceNumber uint64 noX509SVIDKeys bool noJWTSVIDKeys bool + noWITSVIDKeys bool standardJWKS bool } @@ -59,6 +60,14 @@ func NoJWTSVIDKeys() MarshalOption { }) } +// NoWITSVIDKeys skips marshalling WIT SVID keys +func NoWITSVIDKeys() MarshalOption { + return marshalOption(func(c *marshalConfig) error { + c.noWITSVIDKeys = true + return nil + }) +} + // StandardJWKS omits SPIFFE-specific parameters from the marshaled bundle func StandardJWKS() MarshalOption { return marshalOption(func(c *marshalConfig) error { @@ -67,6 +76,25 @@ func StandardJWKS() MarshalOption { }) } +func MarshalX509SVIDBundle(bundle *spiffebundle.Bundle, opts ...MarshalOption) ([]byte, error) { + allOpts := append([]MarshalOption{NoJWTSVIDKeys(), NoWITSVIDKeys()}, opts...) + return Marshal(bundle, allOpts...) +} + +func MarshalJWTSVIDBundle(bundle *spiffebundle.Bundle, opts ...MarshalOption) ([]byte, error) { + allOpts := append([]MarshalOption{NoX509SVIDKeys(), NoWITSVIDKeys()}, opts...) + return Marshal(bundle, allOpts...) +} + +func MarshalWITSVIDBundle(bundle *spiffebundle.Bundle, opts ...MarshalOption) (string, error) { + allOpts := append([]MarshalOption{NoX509SVIDKeys(), NoJWTSVIDKeys()}, opts...) + marshaledBundle, err := Marshal(bundle, allOpts...) + if err != nil { + return "", err + } + return string(marshaledBundle), nil +} + func Marshal(bundle *spiffebundle.Bundle, opts ...MarshalOption) ([]byte, error) { refreshHint, ok := bundle.RefreshHint() if !ok { @@ -118,6 +146,16 @@ func Marshal(bundle *spiffebundle.Bundle, opts ...MarshalOption) ([]byte, error) } } + if !c.noWITSVIDKeys { + for keyID, witSigningKey := range bundle.WITAuthorities() { + jwks.Keys = append(jwks.Keys, jose.JSONWebKey{ + Key: witSigningKey, + KeyID: keyID, + Use: maybeUse(witSVIDUse), + }) + } + } + var out any = jwks if !c.standardJWKS { out = bundleDoc{ diff --git a/pkg/common/bundleutil/marshal_test.go b/pkg/common/bundleutil/marshal_test.go index ae7d2f8633..a94510ae43 100644 --- a/pkg/common/bundleutil/marshal_test.go +++ b/pkg/common/bundleutil/marshal_test.go @@ -14,10 +14,11 @@ func TestMarshal(t *testing.T) { rootCA := createCACertificate(t) testCases := []struct { - name string - empty bool - opts []MarshalOption - out string + name string + empty bool + withWITKey bool + opts []MarshalOption + out string }{ { name: "empty bundle", @@ -136,6 +137,73 @@ func TestMarshal(t *testing.T) { ] }`, x5c(rootCA)), }, + { + name: "without WIT SVID keys", + withWITKey: true, + opts: []MarshalOption{ + NoWITSVIDKeys(), + }, + out: fmt.Sprintf(`{ + "keys": [ + { + "use": "x509-svid", + "kty": "EC", + "crv": "P-256", + "x": "kkEn5E2Hd_rvCRDCVMNj3deN0ADij9uJVmN-El0CJz0", + "y": "qNrnjhtzrtTR0bRgI2jPIC1nEgcWNX63YcZOEzyo1iA", + "x5c": [ + "%s" + ] + }, + { + "use": "jwt-svid", + "kid": "FOO", + "kty": "EC", + "crv": "P-256", + "x": "kkEn5E2Hd_rvCRDCVMNj3deN0ADij9uJVmN-El0CJz0", + "y": "qNrnjhtzrtTR0bRgI2jPIC1nEgcWNX63YcZOEzyo1iA" + } + ], + "spiffe_refresh_hint": 60, + "spiffe_sequence": 42 + }`, x5c(rootCA)), + }, + { + name: "with all SVID keys", + withWITKey: true, + out: fmt.Sprintf(`{ + "keys": [ + { + "use": "x509-svid", + "kty": "EC", + "crv": "P-256", + "x": "kkEn5E2Hd_rvCRDCVMNj3deN0ADij9uJVmN-El0CJz0", + "y": "qNrnjhtzrtTR0bRgI2jPIC1nEgcWNX63YcZOEzyo1iA", + "x5c": [ + "%s" + ] + }, + { + "use": "jwt-svid", + "kid": "FOO", + "kty": "EC", + "crv": "P-256", + "x": "kkEn5E2Hd_rvCRDCVMNj3deN0ADij9uJVmN-El0CJz0", + "y": "qNrnjhtzrtTR0bRgI2jPIC1nEgcWNX63YcZOEzyo1iA" + }, + { + "use": "wit-svid", + "kid": "BAR", + "kty": "EC", + "crv": "P-256", + "x": "kkEn5E2Hd_rvCRDCVMNj3deN0ADij9uJVmN-El0CJz0", + "y": "qNrnjhtzrtTR0bRgI2jPIC1nEgcWNX63YcZOEzyo1iA" + } + ], + "spiffe_refresh_hint": 60, + "spiffe_sequence": 42 + }`, x5c(rootCA)), + }, } trustDomain := spiffeid.RequireTrustDomainFromString("domain.test") @@ -149,9 +217,99 @@ func TestMarshal(t *testing.T) { bundle.AddX509Authority(rootCA) require.NoError(t, bundle.AddJWTAuthority("FOO", testKey.Public())) } + if testCase.withWITKey { + require.NoError(t, bundle.AddWITAuthority("BAR", testKey.Public())) + } bundleBytes, err := Marshal(bundle, testCase.opts...) require.NoError(t, err) require.JSONEq(t, testCase.out, string(bundleBytes)) }) } } + +func TestMarshalX509SVIDBundle(t *testing.T) { + rootCA := createCACertificate(t) + trustDomain := spiffeid.RequireTrustDomainFromString("domain.test") + + bundle := spiffebundle.New(trustDomain) + bundle.SetRefreshHint(time.Minute) + bundle.SetSequenceNumber(42) + bundle.AddX509Authority(rootCA) + require.NoError(t, bundle.AddJWTAuthority("FOO", testKey.Public())) + require.NoError(t, bundle.AddWITAuthority("BAR", testKey.Public())) + + bundleBytes, err := MarshalX509SVIDBundle(bundle) + require.NoError(t, err) + require.JSONEq(t, fmt.Sprintf(`{ + "keys": [ + { + "use": "x509-svid", + "kty": "EC", + "crv": "P-256", + "x": "kkEn5E2Hd_rvCRDCVMNj3deN0ADij9uJVmN-El0CJz0", + "y": "qNrnjhtzrtTR0bRgI2jPIC1nEgcWNX63YcZOEzyo1iA", + "x5c": ["%s"] + } + ], + "spiffe_refresh_hint": 60, + "spiffe_sequence": 42 + }`, x5c(rootCA)), string(bundleBytes)) +} + +func TestMarshalJWTSVIDBundle(t *testing.T) { + rootCA := createCACertificate(t) + trustDomain := spiffeid.RequireTrustDomainFromString("domain.test") + + bundle := spiffebundle.New(trustDomain) + bundle.SetRefreshHint(time.Minute) + bundle.SetSequenceNumber(42) + bundle.AddX509Authority(rootCA) + require.NoError(t, bundle.AddJWTAuthority("FOO", testKey.Public())) + require.NoError(t, bundle.AddWITAuthority("BAR", testKey.Public())) + + bundleBytes, err := MarshalJWTSVIDBundle(bundle) + require.NoError(t, err) + require.JSONEq(t, `{ + "keys": [ + { + "use": "jwt-svid", + "kid": "FOO", + "kty": "EC", + "crv": "P-256", + "x": "kkEn5E2Hd_rvCRDCVMNj3deN0ADij9uJVmN-El0CJz0", + "y": "qNrnjhtzrtTR0bRgI2jPIC1nEgcWNX63YcZOEzyo1iA" + } + ], + "spiffe_refresh_hint": 60, + "spiffe_sequence": 42 + }`, string(bundleBytes)) +} + +func TestMarshalWITSVIDBundle(t *testing.T) { + rootCA := createCACertificate(t) + trustDomain := spiffeid.RequireTrustDomainFromString("domain.test") + + bundle := spiffebundle.New(trustDomain) + bundle.SetRefreshHint(time.Minute) + bundle.SetSequenceNumber(42) + bundle.AddX509Authority(rootCA) + require.NoError(t, bundle.AddJWTAuthority("FOO", testKey.Public())) + require.NoError(t, bundle.AddWITAuthority("BAR", testKey.Public())) + + bundleBytes, err := MarshalWITSVIDBundle(bundle) + require.NoError(t, err) + require.JSONEq(t, `{ + "keys": [ + { + "use": "wit-svid", + "kid": "BAR", + "kty": "EC", + "crv": "P-256", + "x": "kkEn5E2Hd_rvCRDCVMNj3deN0ADij9uJVmN-El0CJz0", + "y": "qNrnjhtzrtTR0bRgI2jPIC1nEgcWNX63YcZOEzyo1iA" + } + ], + "spiffe_refresh_hint": 60, + "spiffe_sequence": 42 + }`, bundleBytes) +} diff --git a/pkg/common/bundleutil/types.go b/pkg/common/bundleutil/types.go index 277b461594..a91a34451d 100644 --- a/pkg/common/bundleutil/types.go +++ b/pkg/common/bundleutil/types.go @@ -7,6 +7,7 @@ import ( const ( x509SVIDUse = "x509-svid" jwtSVIDUse = "jwt-svid" + witSVIDUse = "wit-svid" ) type bundleDoc struct { diff --git a/pkg/common/bundleutil/unmarshal.go b/pkg/common/bundleutil/unmarshal.go index ff86b79a17..3c248c556c 100644 --- a/pkg/common/bundleutil/unmarshal.go +++ b/pkg/common/bundleutil/unmarshal.go @@ -44,6 +44,13 @@ func unmarshal(trustDomain spiffeid.TrustDomain, doc *bundleDoc) (*spiffebundle. if err := bundle.AddJWTAuthority(key.KeyID, key.Key); err != nil { return nil, fmt.Errorf("failed to add jwt-svid entry %d: %w", i, err) } + case witSVIDUse: + if key.KeyID == "" { + return nil, fmt.Errorf("missing key ID in wit-svid entry %d", i) + } + if err := bundle.AddWITAuthority(key.KeyID, key.Key); err != nil { + return nil, fmt.Errorf("failed to add wit-svid entry %d: %w", i, err) + } case "": return nil, fmt.Errorf("missing use for key entry %d", i) default: diff --git a/pkg/common/bundleutil/unmarshal_test.go b/pkg/common/bundleutil/unmarshal_test.go index 5cc7da925f..0b551e86df 100644 --- a/pkg/common/bundleutil/unmarshal_test.go +++ b/pkg/common/bundleutil/unmarshal_test.go @@ -103,6 +103,21 @@ func TestUnmarshal(t *testing.T) { }`, err: "missing key ID in jwt-svid entry 0", }, + { + name: "wit-svid with no keyid", + doc: `{ + "keys": [ + { + "use": "wit-svid", + "kty": "EC", + "crv": "P-256", + "x": "kkEn5E2Hd_rvCRDCVMNj3deN0ADij9uJVmN-El0CJz0", + "y": "qNrnjhtzrtTR0bRgI2jPIC1nEgcWNX63YcZOEzyo1iA" + } + ] + }`, + err: "missing key ID in wit-svid entry 0", + }, } for _, testCase := range testCases { @@ -117,3 +132,17 @@ func TestUnmarshal(t *testing.T) { }) } } + +func TestUnmarshalRoundTripsWITAuthorities(t *testing.T) { + trustDomain := spiffeid.RequireTrustDomainFromString("domain.test") + bundle := spiffebundle.New(trustDomain) + bundle.SetRefreshHint(0) + require.NoError(t, bundle.AddWITAuthority("BAR", testKey.Public())) + + marshaled, err := Marshal(bundle) + require.NoError(t, err) + + unmarshaled, err := Unmarshal(trustDomain, marshaled) + require.NoError(t, err) + require.Equal(t, bundle, unmarshaled) +}