Skip to content

Commit d543174

Browse files
feat(sdk): introduce SigningAlgorithm type for WithDPoPAlgorithm
Replace the raw string parameter in WithDPoPAlgorithm with a named SigningAlgorithm type (RFC 7518 §3.1) and export the six supported constants (ES256, ES384, ES512, RS256, RS384, RS512) from the sdk package. This guides correct use via IDE autocomplete and prevents untyped string variables from being passed without an explicit cast. Runtime validation via generateDPoPKeyForAlg's default error case is unchanged. Remove the private dpopAlgES*/RS* string constants from dpop_key.go in favour of the exported type. Update the otdfctl CLI caller to cast the flag string. Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
1 parent 5ad3911 commit d543174

6 files changed

Lines changed: 55 additions & 52 deletions

File tree

otdfctl/cmd/tdf/dpop.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ func dpopSDKOpts(c *cli.Cli) []sdk.Option {
2121
}
2222
opts = append(opts, sdk.WithDPoPKeyPEM(pemBytes))
2323
if dpopAlg != "" {
24-
opts = append(opts, sdk.WithDPoPAlgorithm(dpopAlg))
24+
opts = append(opts, sdk.WithDPoPAlgorithm(sdk.SigningAlgorithm(dpopAlg)))
2525
}
2626
} else if dpopAlg != "" {
27-
opts = append(opts, sdk.WithDPoPAlgorithm(dpopAlg))
27+
opts = append(opts, sdk.WithDPoPAlgorithm(sdk.SigningAlgorithm(dpopAlg)))
2828
}
2929
return opts
3030
}

sdk/dpop_key.go

Lines changed: 12 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,34 +13,24 @@ import (
1313
"github.com/lestrrat-go/jwx/v2/jwk"
1414
)
1515

16-
// Supported DPoP algorithm identifiers (RFC 9449 §4.2).
17-
const (
18-
dpopAlgES256 = "ES256"
19-
dpopAlgES384 = "ES384"
20-
dpopAlgES512 = "ES512"
21-
dpopAlgRS256 = "RS256"
22-
dpopAlgRS384 = "RS384"
23-
dpopAlgRS512 = "RS512"
24-
)
25-
26-
const dpopAllowedAlgs = dpopAlgES256 + ", " + dpopAlgES384 + ", " + dpopAlgES512 + ", " +
27-
dpopAlgRS256 + ", " + dpopAlgRS384 + ", " + dpopAlgRS512
16+
const dpopAllowedAlgs = string(ES256) + ", " + string(ES384) + ", " + string(ES512) + ", " +
17+
string(RS256) + ", " + string(RS384) + ", " + string(RS512)
2818

2919
// generateDPoPKeyForAlg generates an ephemeral DPoP private key for the given algorithm.
3020
// Supported algorithms: ES256, ES384, ES512, RS256, RS384, RS512.
31-
func generateDPoPKeyForAlg(alg string) (jwk.Key, error) {
21+
func generateDPoPKeyForAlg(alg SigningAlgorithm) (jwk.Key, error) {
3222
switch alg {
33-
case dpopAlgES256:
23+
case ES256:
3424
return generateECDSAKey(elliptic.P256(), jwa.ES256)
35-
case dpopAlgES384:
25+
case ES384:
3626
return generateECDSAKey(elliptic.P384(), jwa.ES384)
37-
case dpopAlgES512:
27+
case ES512:
3828
return generateECDSAKey(elliptic.P521(), jwa.ES512)
39-
case dpopAlgRS256:
29+
case RS256:
4030
return generateRSAKey(jwa.RS256)
41-
case dpopAlgRS384:
31+
case RS384:
4232
return generateRSAKey(jwa.RS384)
43-
case dpopAlgRS512:
33+
case RS512:
4434
return generateRSAKey(jwa.RS512)
4535
default:
4636
return nil, fmt.Errorf("unsupported DPoP algorithm %q; allowed: %s", alg, dpopAllowedAlgs)
@@ -151,8 +141,8 @@ func validateDPoPKey(key jwk.Key) error {
151141
return errors.New("DPoP JWK is missing required Algorithm field; set it with key.Set(jwk.AlgorithmKey, ...)")
152142
}
153143
algStr := alg.String()
154-
switch algStr {
155-
case dpopAlgES256, dpopAlgES384, dpopAlgES512, dpopAlgRS256, dpopAlgRS384, dpopAlgRS512:
144+
switch SigningAlgorithm(algStr) {
145+
case ES256, ES384, ES512, RS256, RS384, RS512:
156146
// supported
157147
default:
158148
return fmt.Errorf("unsupported DPoP JWK algorithm %q; allowed: %s", algStr, dpopAllowedAlgs)
@@ -234,7 +224,7 @@ func selectDPoPKey(c *config) (jwk.Key, error) {
234224
}
235225
if c.dpopAlgorithm != "" {
236226
var algVal jwa.SignatureAlgorithm
237-
if err := algVal.Accept(c.dpopAlgorithm); err != nil {
227+
if err := algVal.Accept(string(c.dpopAlgorithm)); err != nil {
238228
return nil, fmt.Errorf("invalid DPoP algorithm override %q: %w", c.dpopAlgorithm, err)
239229
}
240230
if err := key.Set(jwk.AlgorithmKey, algVal); err != nil {

sdk/dpop_key_test.go

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -48,17 +48,17 @@ func publicKeyPEMForTest(t *testing.T, key interface{ Raw(any) error }) []byte {
4848

4949
func TestGenerateDPoPKeyForAlg_EC(t *testing.T) {
5050
tests := []struct {
51-
alg string
51+
alg SigningAlgorithm
5252
wantAlg jwa.SignatureAlgorithm
5353
curve elliptic.Curve
5454
}{
55-
{dpopAlgES256, jwa.ES256, elliptic.P256()},
56-
{dpopAlgES384, jwa.ES384, elliptic.P384()},
57-
{dpopAlgES512, jwa.ES512, elliptic.P521()},
55+
{ES256, jwa.ES256, elliptic.P256()},
56+
{ES384, jwa.ES384, elliptic.P384()},
57+
{ES512, jwa.ES512, elliptic.P521()},
5858
}
5959

6060
for _, tt := range tests {
61-
t.Run(tt.alg, func(t *testing.T) {
61+
t.Run(string(tt.alg), func(t *testing.T) {
6262
key, err := generateDPoPKeyForAlg(tt.alg)
6363
require.NoErrorf(t, err, "generateDPoPKeyForAlg(%q)", tt.alg)
6464
assert.Equal(t, tt.wantAlg, key.Algorithm(), "algorithm")
@@ -71,16 +71,16 @@ func TestGenerateDPoPKeyForAlg_EC(t *testing.T) {
7171

7272
func TestGenerateDPoPKeyForAlg_RSA(t *testing.T) {
7373
tests := []struct {
74-
alg string
74+
alg SigningAlgorithm
7575
wantAlg jwa.SignatureAlgorithm
7676
}{
77-
{dpopAlgRS256, jwa.RS256},
78-
{dpopAlgRS384, jwa.RS384},
79-
{dpopAlgRS512, jwa.RS512},
77+
{RS256, jwa.RS256},
78+
{RS384, jwa.RS384},
79+
{RS512, jwa.RS512},
8080
}
8181

8282
for _, tt := range tests {
83-
t.Run(tt.alg, func(t *testing.T) {
83+
t.Run(string(tt.alg), func(t *testing.T) {
8484
key, err := generateDPoPKeyForAlg(tt.alg)
8585
require.NoErrorf(t, err, "generateDPoPKeyForAlg(%q)", tt.alg)
8686
assert.Equal(t, tt.wantAlg, key.Algorithm(), "algorithm")
@@ -91,16 +91,16 @@ func TestGenerateDPoPKeyForAlg_RSA(t *testing.T) {
9191
}
9292

9393
func TestGenerateDPoPKeyForAlg_Invalid(t *testing.T) {
94-
for _, alg := range []string{"INVALID", "", "HS256", "PS256"} {
95-
t.Run(alg, func(t *testing.T) {
94+
for _, alg := range []SigningAlgorithm{"INVALID", "", "HS256", "PS256"} {
95+
t.Run(string(alg), func(t *testing.T) {
9696
_, err := generateDPoPKeyForAlg(alg)
9797
assert.Errorf(t, err, "expected error for alg %q", alg)
9898
})
9999
}
100100
}
101101

102102
func TestLoadDPoPKeyFromPEM_RSA(t *testing.T) {
103-
generated, err := generateDPoPKeyForAlg(dpopAlgRS256)
103+
generated, err := generateDPoPKeyForAlg(RS256)
104104
require.NoError(t, err, "failed to generate RSA test key")
105105
pemBytes := jwkToPEMForTest(t, generated)
106106

@@ -111,16 +111,16 @@ func TestLoadDPoPKeyFromPEM_RSA(t *testing.T) {
111111

112112
func TestLoadDPoPKeyFromPEM_EC(t *testing.T) {
113113
tests := []struct {
114-
alg string
114+
alg SigningAlgorithm
115115
wantAlg jwa.SignatureAlgorithm
116116
}{
117-
{dpopAlgES256, jwa.ES256},
118-
{dpopAlgES384, jwa.ES384},
119-
{dpopAlgES512, jwa.ES512},
117+
{ES256, jwa.ES256},
118+
{ES384, jwa.ES384},
119+
{ES512, jwa.ES512},
120120
}
121121

122122
for _, tt := range tests {
123-
t.Run(tt.alg, func(t *testing.T) {
123+
t.Run(string(tt.alg), func(t *testing.T) {
124124
generated, err := generateDPoPKeyForAlg(tt.alg)
125125
require.NoError(t, err, "failed to generate EC test key")
126126
pemBytes := jwkToPEMForTest(t, generated)
@@ -138,8 +138,8 @@ func TestLoadDPoPKeyFromPEM_InvalidPEM(t *testing.T) {
138138
}
139139

140140
func TestLoadDPoPKeyFromPEM_PublicKeyRejected(t *testing.T) {
141-
for _, alg := range []string{dpopAlgRS256, dpopAlgES256} {
142-
t.Run(alg, func(t *testing.T) {
141+
for _, alg := range []SigningAlgorithm{RS256, ES256} {
142+
t.Run(string(alg), func(t *testing.T) {
143143
generated, err := generateDPoPKeyForAlg(alg)
144144
require.NoError(t, err, "generate test key")
145145
pubPEM := publicKeyPEMForTest(t, generated)
@@ -152,7 +152,7 @@ func TestLoadDPoPKeyFromPEM_PublicKeyRejected(t *testing.T) {
152152
}
153153

154154
func TestResolveDPoPKey(t *testing.T) {
155-
ecKey, err := generateDPoPKeyForAlg(dpopAlgES256)
155+
ecKey, err := generateDPoPKeyForAlg(ES256)
156156
require.NoError(t, err, "generate EC key")
157157
ecPEM := jwkToPEMForTest(t, ecKey)
158158

@@ -177,16 +177,16 @@ func TestResolveDPoPKey(t *testing.T) {
177177
})
178178

179179
t.Run("PEM with algorithm override", func(t *testing.T) {
180-
rsaKey, err := generateDPoPKeyForAlg(dpopAlgRS256)
180+
rsaKey, err := generateDPoPKeyForAlg(RS256)
181181
require.NoError(t, err, "generate RSA key")
182-
c := &config{dpopKeyPEM: jwkToPEMForTest(t, rsaKey), dpopAlgorithm: dpopAlgRS512}
182+
c := &config{dpopKeyPEM: jwkToPEMForTest(t, rsaKey), dpopAlgorithm: RS512}
183183
key, err := resolveDPoPKey(c)
184184
require.NoError(t, err)
185185
assert.Equal(t, jwa.RS512, key.Algorithm(), "alg override")
186186
})
187187

188188
t.Run("generate from algorithm", func(t *testing.T) {
189-
key, err := resolveDPoPKey(&config{dpopAlgorithm: dpopAlgES384})
189+
key, err := resolveDPoPKey(&config{dpopAlgorithm: ES384})
190190
require.NoError(t, err)
191191
assert.Equal(t, jwa.ES384, key.Algorithm(), "alg")
192192
})

sdk/dpop_validation_client_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ func TestNewDPoPValidationHTTPClient(t *testing.T) {
2222
}))
2323
defer server.Close()
2424

25-
client, err := NewDPoPValidationHTTPClient(http.DefaultClient, WithDPoPAlgorithm(dpopAlgES256))
25+
client, err := NewDPoPValidationHTTPClient(http.DefaultClient, WithDPoPAlgorithm(ES256))
2626
require.NoError(t, err, "NewDPoPValidationHTTPClient")
2727

2828
resp, err := client.Do(mustGet(t, server.URL))

sdk/options.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,19 @@ import (
1717

1818
type Option func(*config)
1919

20+
// SigningAlgorithm identifies a JWS signing algorithm (RFC 7518 §3.1).
21+
type SigningAlgorithm string
22+
23+
// Supported JWS signing algorithms for DPoP proof tokens (RFC 9449 §4.2).
24+
const (
25+
ES256 SigningAlgorithm = "ES256"
26+
ES384 SigningAlgorithm = "ES384"
27+
ES512 SigningAlgorithm = "ES512"
28+
RS256 SigningAlgorithm = "RS256"
29+
RS384 SigningAlgorithm = "RS384"
30+
RS512 SigningAlgorithm = "RS512"
31+
)
32+
2033
type ConnectRPCConnection struct {
2134
Client *http.Client
2235
Endpoint string
@@ -37,7 +50,7 @@ type config struct {
3750
kasSessionKey *ocrypto.RsaKeyPair
3851
dpopKey *ocrypto.RsaKeyPair
3952
dpopJWK jwk.Key
40-
dpopAlgorithm string
53+
dpopAlgorithm SigningAlgorithm
4154
dpopKeyPEM []byte
4255
ipc bool
4356
tdfFeatures tdfFeatures
@@ -237,7 +250,7 @@ func WithLogger(logger *slog.Logger) Option {
237250
// Supported: ES256, ES384, ES512, RS256, RS384, RS512.
238251
// When this option is unset the SDK falls back to an auto-generated RSA key; ES256 is the
239252
// recommended choice and is what the otdfctl CLI uses for a bare --dpop flag.
240-
func WithDPoPAlgorithm(alg string) Option {
253+
func WithDPoPAlgorithm(alg SigningAlgorithm) Option {
241254
return func(c *config) {
242255
c.dpopAlgorithm = alg
243256
}

sdk/sdk.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ func buildIDPTokenSource(c *config) (auth.AccessTokenSource, jwk.Key, error) {
345345

346346
// No DPoP key configured: auto-generate a default ephemeral ES256/P-256 key.
347347
if dpopKey == nil {
348-
dpopKey, err = generateDPoPKeyForAlg(dpopAlgES256)
348+
dpopKey, err = generateDPoPKeyForAlg(ES256)
349349
if err != nil {
350350
return nil, nil, fmt.Errorf("failed to generate default DPoP key: %w", err)
351351
}

0 commit comments

Comments
 (0)