Skip to content

Commit 0a27cba

Browse files
authored
WFE/nonce: Add NonceHMACKey field (#7793)
Add a new WFE & nonce config field, `NonceHMACKey`, which uses the new `cmd.HMACKeyConfig` type. Deprecate the `NoncePrefixKey` config field. Generalize the error message when validating `HMACKeyConfig` in `config`. Remove the deprecated `UseDerivablePrefix` config field, which is no longer used anywhere. Part of #7632
1 parent 5be3e99 commit 0a27cba

File tree

17 files changed

+77
-53
lines changed

17 files changed

+77
-53
lines changed

cmd/boulder-wfe2/main.go

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,23 @@ type Config struct {
7171
// local and remote nonce-service instances.
7272
RedeemNonceService *cmd.GRPCClientConfig `validate:"required"`
7373

74+
// NonceHMACKey is a path to a file containing an HMAC key which is a
75+
// secret used for deriving the prefix of each nonce instance. It should
76+
// contain 256 bits (32 bytes) of random data to be suitable as an
77+
// HMAC-SHA256 key (e.g. the output of `openssl rand -hex 32`). In a
78+
// multi-DC deployment this value should be the same across all
79+
// boulder-wfe and nonce-service instances.
80+
NonceHMACKey cmd.HMACKeyConfig `validate:"-"`
81+
7482
// NoncePrefixKey is a secret used for deriving the prefix of each nonce
7583
// instance. It should contain 256 bits of random data to be suitable as
7684
// an HMAC-SHA256 key (e.g. the output of `openssl rand -hex 32`). In a
7785
// multi-DC deployment this value should be the same across all
7886
// boulder-wfe and nonce-service instances.
7987
//
80-
// TODO(#7632) Update this to use the new HMACKeyConfig.
88+
// TODO(#7632): Remove this.
89+
//
90+
// Deprecated: Use NonceHMACKey instead.
8191
NoncePrefixKey cmd.PasswordConfig `validate:"-"`
8292

8393
// Chains is a list of lists of certificate filenames. Each inner list is
@@ -294,10 +304,16 @@ func main() {
294304
cmd.Fail("'getNonceService' must be configured")
295305
}
296306

297-
var noncePrefixKey string
298-
if c.WFE.NoncePrefixKey.PasswordFile != "" {
299-
noncePrefixKey, err = c.WFE.NoncePrefixKey.Pass()
300-
cmd.FailOnError(err, "Failed to load noncePrefixKey")
307+
var noncePrefixKey []byte
308+
if c.WFE.NonceHMACKey.KeyFile != "" {
309+
noncePrefixKey, err = c.WFE.NonceHMACKey.Load()
310+
cmd.FailOnError(err, "Failed to load nonceHMACKey file")
311+
} else if c.WFE.NoncePrefixKey.PasswordFile != "" {
312+
keyString, err := c.WFE.NoncePrefixKey.Pass()
313+
cmd.FailOnError(err, "Failed to load noncePrefixKey file")
314+
noncePrefixKey = []byte(keyString)
315+
} else {
316+
cmd.Fail("NonceHMACKey KeyFile or NoncePrefixKey PasswordFile must be set")
301317
}
302318

303319
getNonceConn, err := bgrpc.ClientSetup(c.WFE.GetNonceService, tlsConfig, stats, clk)

cmd/config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -572,7 +572,7 @@ func (hc *HMACKeyConfig) Load() ([]byte, error) {
572572

573573
if len(trimmed) != 32 {
574574
return nil, fmt.Errorf(
575-
"validating unpauseHMACKey, length must be 32 alphanumeric characters, got %d",
575+
"validating HMAC key, length must be 32 alphanumeric characters, got %d",
576576
len(trimmed),
577577
)
578578
}

cmd/nonce-service/main.go

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,30 +19,32 @@ type Config struct {
1919

2020
MaxUsed int
2121

22-
// UseDerivablePrefix indicates whether to use a nonce prefix derived
23-
// from the gRPC listening address. If this is false, the nonce prefix
24-
// will be the value of the NoncePrefix field. If this is true, the
25-
// NoncePrefixKey field is required.
26-
// TODO(#6610): Remove this.
27-
//
28-
// Deprecated: this value is ignored, and treated as though it is always true.
29-
UseDerivablePrefix bool `validate:"-"`
22+
// NonceHMACKey is a path to a file containing an HMAC key which is a
23+
// secret used for deriving the prefix of each nonce instance. It should
24+
// contain 256 bits (32 bytes) of random data to be suitable as an
25+
// HMAC-SHA256 key (e.g. the output of `openssl rand -hex 32`). In a
26+
// multi-DC deployment this value should be the same across all
27+
// boulder-wfe and nonce-service instances.
28+
NonceHMACKey cmd.HMACKeyConfig `validate:"required_without_all=NoncePrefixKey,structonly"`
3029

3130
// NoncePrefixKey is a secret used for deriving the prefix of each nonce
3231
// instance. It should contain 256 bits (32 bytes) of random data to be
3332
// suitable as an HMAC-SHA256 key (e.g. the output of `openssl rand -hex
3433
// 32`). In a multi-DC deployment this value should be the same across
3534
// all boulder-wfe and nonce-service instances.
3635
//
37-
// TODO(#7632) Update this to use the new HMACKeyConfig.
38-
NoncePrefixKey cmd.PasswordConfig `validate:"required"`
36+
// TODO(#7632): Remove this and change `NonceHMACKey`'s validation to
37+
// just `required.`
38+
//
39+
// Deprecated: Use NonceHMACKey instead.
40+
NoncePrefixKey cmd.PasswordConfig `validate:"required_without_all=NonceHMACKey,structonly"`
3941

4042
Syslog cmd.SyslogConfig
4143
OpenTelemetry cmd.OpenTelemetryConfig
4244
}
4345
}
4446

45-
func derivePrefix(key string, grpcAddr string) (string, error) {
47+
func derivePrefix(key []byte, grpcAddr string) (string, error) {
4648
host, port, err := net.SplitHostPort(grpcAddr)
4749
if err != nil {
4850
return "", fmt.Errorf("parsing gRPC listen address: %w", err)
@@ -84,12 +86,18 @@ func main() {
8486
c.NonceService.DebugAddr = *debugAddr
8587
}
8688

87-
if c.NonceService.NoncePrefixKey.PasswordFile == "" {
88-
cmd.Fail("NoncePrefixKey PasswordFile must be set")
89+
var key []byte
90+
if c.NonceService.NonceHMACKey.KeyFile != "" {
91+
key, err = c.NonceService.NonceHMACKey.Load()
92+
cmd.FailOnError(err, "Failed to load 'nonceHMACKey' file.")
93+
} else if c.NonceService.NoncePrefixKey.PasswordFile != "" {
94+
keyString, err := c.NonceService.NoncePrefixKey.Pass()
95+
cmd.FailOnError(err, "Failed to load 'noncePrefixKey' file.")
96+
key = []byte(keyString)
97+
} else {
98+
cmd.Fail("NonceHMACKey KeyFile or NoncePrefixKey PasswordFile must be set")
8999
}
90100

91-
key, err := c.NonceService.NoncePrefixKey.Pass()
92-
cmd.FailOnError(err, "Failed to load 'noncePrefixKey' file.")
93101
noncePrefix, err := derivePrefix(key, c.NonceService.GRPC.Address)
94102
cmd.FailOnError(err, "Failed to derive nonce prefix")
95103

grpc/noncebalancer/noncebalancer.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ var ErrNoBackendsMatchPrefix = status.New(codes.Unavailable, "no backends match
3535
var errMissingPrefixCtxKey = errors.New("nonce.PrefixCtxKey value required in RPC context")
3636
var errMissingHMACKeyCtxKey = errors.New("nonce.HMACKeyCtxKey value required in RPC context")
3737
var errInvalidPrefixCtxKeyType = errors.New("nonce.PrefixCtxKey value in RPC context must be a string")
38-
var errInvalidHMACKeyCtxKeyType = errors.New("nonce.HMACKeyCtxKey value in RPC context must be a string")
38+
var errInvalidHMACKeyCtxKeyType = errors.New("nonce.HMACKeyCtxKey value in RPC context must be a byte slice")
3939

4040
// Balancer implements the base.PickerBuilder interface. It's used to create new
4141
// balancer.Picker instances. It should only be used by nonce-service clients.
@@ -84,7 +84,7 @@ func (p *Picker) Pick(info balancer.PickInfo) (balancer.PickResult, error) {
8484
// This should never happen.
8585
return balancer.PickResult{}, errMissingHMACKeyCtxKey
8686
}
87-
hmacKey, ok := hmacKeyVal.(string)
87+
hmacKey, ok := hmacKeyVal.([]byte)
8888
if !ok {
8989
// This should never happen.
9090
return balancer.PickResult{}, errInvalidHMACKeyCtxKeyType

grpc/noncebalancer/noncebalancer_test.go

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,20 @@ import (
44
"context"
55
"testing"
66

7-
"github.com/letsencrypt/boulder/nonce"
8-
"github.com/letsencrypt/boulder/test"
97
"google.golang.org/grpc/balancer"
108
"google.golang.org/grpc/balancer/base"
119
"google.golang.org/grpc/resolver"
10+
11+
"github.com/letsencrypt/boulder/nonce"
12+
"github.com/letsencrypt/boulder/test"
1213
)
1314

1415
func TestPickerPicksCorrectBackend(t *testing.T) {
1516
_, p, subConns := setupTest(false)
16-
prefix := nonce.DerivePrefix(subConns[0].addrs[0].Addr, "Kala namak")
17+
prefix := nonce.DerivePrefix(subConns[0].addrs[0].Addr, []byte("Kala namak"))
1718

1819
testCtx := context.WithValue(context.Background(), nonce.PrefixCtxKey{}, "HNmOnt8w")
19-
testCtx = context.WithValue(testCtx, nonce.HMACKeyCtxKey{}, prefix)
20+
testCtx = context.WithValue(testCtx, nonce.HMACKeyCtxKey{}, []byte(prefix))
2021
info := balancer.PickInfo{Ctx: testCtx}
2122

2223
gotPick, err := p.Pick(info)
@@ -26,9 +27,9 @@ func TestPickerPicksCorrectBackend(t *testing.T) {
2627

2728
func TestPickerMissingPrefixInCtx(t *testing.T) {
2829
_, p, subConns := setupTest(false)
29-
prefix := nonce.DerivePrefix(subConns[0].addrs[0].Addr, "Kala namak")
30+
prefix := nonce.DerivePrefix(subConns[0].addrs[0].Addr, []byte("Kala namak"))
3031

31-
testCtx := context.WithValue(context.Background(), nonce.HMACKeyCtxKey{}, prefix)
32+
testCtx := context.WithValue(context.Background(), nonce.HMACKeyCtxKey{}, []byte(prefix))
3233
info := balancer.PickInfo{Ctx: testCtx}
3334

3435
gotPick, err := p.Pick(info)
@@ -40,7 +41,7 @@ func TestPickerInvalidPrefixInCtx(t *testing.T) {
4041
_, p, _ := setupTest(false)
4142

4243
testCtx := context.WithValue(context.Background(), nonce.PrefixCtxKey{}, 9)
43-
testCtx = context.WithValue(testCtx, nonce.HMACKeyCtxKey{}, "foobar")
44+
testCtx = context.WithValue(testCtx, nonce.HMACKeyCtxKey{}, []byte("foobar"))
4445
info := balancer.PickInfo{Ctx: testCtx}
4546

4647
gotPick, err := p.Pick(info)
@@ -73,10 +74,10 @@ func TestPickerInvalidHMACKeyInCtx(t *testing.T) {
7374

7475
func TestPickerNoMatchingSubConnAvailable(t *testing.T) {
7576
_, p, subConns := setupTest(false)
76-
prefix := nonce.DerivePrefix(subConns[0].addrs[0].Addr, "Kala namak")
77+
prefix := nonce.DerivePrefix(subConns[0].addrs[0].Addr, []byte("Kala namak"))
7778

7879
testCtx := context.WithValue(context.Background(), nonce.PrefixCtxKey{}, "rUsTrUin")
79-
testCtx = context.WithValue(testCtx, nonce.HMACKeyCtxKey{}, prefix)
80+
testCtx = context.WithValue(testCtx, nonce.HMACKeyCtxKey{}, []byte(prefix))
8081
info := balancer.PickInfo{Ctx: testCtx}
8182

8283
gotPick, err := p.Pick(info)

nonce/nonce.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ type HMACKeyCtxKey struct{}
5555
// DerivePrefix derives a nonce prefix from the provided listening address and
5656
// key. The prefix is derived by take the first 8 characters of the base64url
5757
// encoded HMAC-SHA256 hash of the listening address using the provided key.
58-
func DerivePrefix(grpcAddr, key string) string {
59-
h := hmac.New(sha256.New, []byte(key))
58+
func DerivePrefix(grpcAddr string, key []byte) string {
59+
h := hmac.New(sha256.New, key)
6060
h.Write([]byte(grpcAddr))
6161
return base64.RawURLEncoding.EncodeToString(h.Sum(nil))[:PrefixLen]
6262
}

nonce/nonce_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,6 @@ func TestNoncePrefixValidation(t *testing.T) {
147147
}
148148

149149
func TestDerivePrefix(t *testing.T) {
150-
prefix := DerivePrefix("192.168.1.1:8080", "3b8c758dd85e113ea340ce0b3a99f389d40a308548af94d1730a7692c1874f1f")
150+
prefix := DerivePrefix("192.168.1.1:8080", []byte("3b8c758dd85e113ea340ce0b3a99f389d40a308548af94d1730a7692c1874f1f"))
151151
test.AssertEquals(t, prefix, "P9qQaK4o")
152152
}

test/config-next/nonce-a.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"NonceService": {
33
"maxUsed": 131072,
4-
"noncePrefixKey": {
5-
"passwordFile": "test/secrets/nonce_prefix_key"
4+
"nonceHMACKey": {
5+
"keyFile": "test/secrets/nonce_prefix_key"
66
},
77
"syslog": {
88
"stdoutLevel": 6,

test/config-next/nonce-b.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"NonceService": {
33
"maxUsed": 131072,
4-
"noncePrefixKey": {
5-
"passwordFile": "test/secrets/nonce_prefix_key"
4+
"nonceHMACKey": {
5+
"keyFile": "test/secrets/nonce_prefix_key"
66
},
77
"syslog": {
88
"stdoutLevel": 6,

test/config-next/wfe2.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@
6868
"noWaitForReady": true,
6969
"hostOverride": "nonce.boulder"
7070
},
71-
"noncePrefixKey": {
72-
"passwordFile": "test/secrets/nonce_prefix_key"
71+
"nonceHMACKey": {
72+
"keyFile": "test/secrets/nonce_prefix_key"
7373
},
7474
"chains": [
7575
[

0 commit comments

Comments
 (0)