Switch to more efficient multi nonce-service design (#4308)
Basically a complete re-write/re-design of the forwarding concept introduced in #4297 (sorry for the rapid churn here). Instead of nonce-services blindly forwarding nonces around to each other in an attempt to find out who issued the nonce we add an identifying prefix to each nonce generated by a service. The WFEs then use this prefix to decide which nonce-service to ask to validate the nonce. This requires a slightly more complicated configuration at the WFE/2 end, but overall I think ends up being a way cleaner, more understandable, easy to reason about implementation. When configuring the WFE you need to provide two forms of gRPC config: * one gRPC config for retrieving nonces, this should be a DNS name that resolves to all available nonce-services (or at least the ones you want to retrieve nonces from locally, in a two DC setup you might only configure the nonce-services that are in the same DC as the WFE instance). This allows getting a nonce from any of the configured services and is load-balanced transparently at the gRPC layer. * a map of nonce prefixes to gRPC configs, this maps each individual nonce-service to it's prefix and allows the WFE instances to figure out which nonce-service to ask to validate a nonce it has received (in a two DC setup you'd want to configure this with all the nonce-services across both DCs so that you can validate a nonce that was generated by a nonce-service in another DC). This balancing is implemented in the integration tests. Given the current remote nonce code hasn't been deployed anywhere yet this makes a number of hard breaking changes to both the existing nonce-service code, and the forwarding code. Fixes #4303.
This commit is contained in:
parent
9094862051
commit
af41bea99a
|
@ -41,9 +41,17 @@ type config struct {
|
|||
|
||||
TLS cmd.TLSConfig
|
||||
|
||||
RAService *cmd.GRPCClientConfig
|
||||
SAService *cmd.GRPCClientConfig
|
||||
NonceService *cmd.GRPCClientConfig
|
||||
RAService *cmd.GRPCClientConfig
|
||||
SAService *cmd.GRPCClientConfig
|
||||
// GetNonceService contains a gRPC config for any nonce-service instances
|
||||
// which we want to retrieve nonces from. In a multi-DC deployment this
|
||||
// should refer to any local nonce-service instances.
|
||||
GetNonceService *cmd.GRPCClientConfig
|
||||
// RedeemNonceServices contains a map of nonce-service prefixes to
|
||||
// gRPC configs we want to use to redeem nonces. In a multi-DC deployment
|
||||
// this should contain all nonce-services from all DCs as we want to be
|
||||
// able to redeem nonces generated at any DC.
|
||||
RedeemNonceServices map[string]cmd.GRPCClientConfig
|
||||
|
||||
Features map[string]bool
|
||||
|
||||
|
@ -63,7 +71,7 @@ type config struct {
|
|||
}
|
||||
}
|
||||
|
||||
func setupWFE(c config, logger blog.Logger, stats metrics.Scope, clk clock.Clock) (core.RegistrationAuthority, core.StorageAuthority, noncepb.NonceServiceClient) {
|
||||
func setupWFE(c config, logger blog.Logger, stats metrics.Scope, clk clock.Clock) (core.RegistrationAuthority, core.StorageAuthority, noncepb.NonceServiceClient, map[string]noncepb.NonceServiceClient) {
|
||||
tlsConfig, err := c.WFE.TLS.Load()
|
||||
cmd.FailOnError(err, "TLS config")
|
||||
|
||||
|
@ -77,13 +85,19 @@ func setupWFE(c config, logger blog.Logger, stats metrics.Scope, clk clock.Clock
|
|||
sac := bgrpc.NewStorageAuthorityClient(sapb.NewStorageAuthorityClient(saConn))
|
||||
|
||||
var rns noncepb.NonceServiceClient
|
||||
if c.WFE.NonceService != nil {
|
||||
rnsConn, err := bgrpc.ClientSetup(c.WFE.NonceService, tlsConfig, clientMetrics, clk)
|
||||
cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to Nonce service")
|
||||
npm := map[string]noncepb.NonceServiceClient{}
|
||||
if c.WFE.GetNonceService != nil {
|
||||
rnsConn, err := bgrpc.ClientSetup(c.WFE.GetNonceService, tlsConfig, clientMetrics, clk)
|
||||
cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to get nonce service")
|
||||
rns = noncepb.NewNonceServiceClient(rnsConn)
|
||||
for prefix, serviceConfig := range c.WFE.RedeemNonceServices {
|
||||
conn, err := bgrpc.ClientSetup(&serviceConfig, tlsConfig, clientMetrics, clk)
|
||||
cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to redeem nonce service")
|
||||
npm[prefix] = noncepb.NewNonceServiceClient(conn)
|
||||
}
|
||||
}
|
||||
|
||||
return rac, sac, rns
|
||||
return rac, sac, rns, npm
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
@ -109,8 +123,8 @@ func main() {
|
|||
|
||||
kp, err := goodkey.NewKeyPolicy("") // don't load any weak keys
|
||||
cmd.FailOnError(err, "Unable to create key policy")
|
||||
rac, sac, rns := setupWFE(c, logger, scope, clk)
|
||||
wfe, err := wfe.NewWebFrontEndImpl(scope, clk, kp, rns, logger)
|
||||
rac, sac, rns, npm := setupWFE(c, logger, scope, clk)
|
||||
wfe, err := wfe.NewWebFrontEndImpl(scope, clk, kp, rns, npm, logger)
|
||||
cmd.FailOnError(err, "Unable to create WFE")
|
||||
wfe.RA = rac
|
||||
wfe.SA = sac
|
||||
|
|
|
@ -45,9 +45,17 @@ type config struct {
|
|||
|
||||
TLS cmd.TLSConfig
|
||||
|
||||
RAService *cmd.GRPCClientConfig
|
||||
SAService *cmd.GRPCClientConfig
|
||||
NonceService *cmd.GRPCClientConfig
|
||||
RAService *cmd.GRPCClientConfig
|
||||
SAService *cmd.GRPCClientConfig
|
||||
// GetNonceService contains a gRPC config for any nonce-service instances
|
||||
// which we want to retrieve nonces from. In a multi-DC deployment this
|
||||
// should refer to any local nonce-service instances.
|
||||
GetNonceService *cmd.GRPCClientConfig
|
||||
// RedeemNonceServices contains a map of nonce-service prefixes to
|
||||
// gRPC configs we want to use to redeem nonces. In a multi-DC deployment
|
||||
// this should contain all nonce-services from all DCs as we want to be
|
||||
// able to redeem nonces generated at any DC.
|
||||
RedeemNonceServices map[string]cmd.GRPCClientConfig
|
||||
|
||||
// CertificateChains maps AIA issuer URLs to certificate filenames.
|
||||
// Certificates are read into the chain in the order they are defined in the
|
||||
|
@ -182,7 +190,7 @@ func loadCertificateChains(chainConfig map[string][]string) (map[string][]byte,
|
|||
return results, nil
|
||||
}
|
||||
|
||||
func setupWFE(c config, logger blog.Logger, stats metrics.Scope, clk clock.Clock) (core.RegistrationAuthority, core.StorageAuthority, noncepb.NonceServiceClient) {
|
||||
func setupWFE(c config, logger blog.Logger, stats metrics.Scope, clk clock.Clock) (core.RegistrationAuthority, core.StorageAuthority, noncepb.NonceServiceClient, map[string]noncepb.NonceServiceClient) {
|
||||
tlsConfig, err := c.WFE.TLS.Load()
|
||||
cmd.FailOnError(err, "TLS config")
|
||||
clientMetrics := bgrpc.NewClientMetrics(stats)
|
||||
|
@ -195,13 +203,19 @@ func setupWFE(c config, logger blog.Logger, stats metrics.Scope, clk clock.Clock
|
|||
sac := bgrpc.NewStorageAuthorityClient(sapb.NewStorageAuthorityClient(saConn))
|
||||
|
||||
var rns noncepb.NonceServiceClient
|
||||
if c.WFE.NonceService != nil {
|
||||
rnsConn, err := bgrpc.ClientSetup(c.WFE.NonceService, tlsConfig, clientMetrics, clk)
|
||||
cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to Nonce service")
|
||||
npm := map[string]noncepb.NonceServiceClient{}
|
||||
if c.WFE.GetNonceService != nil {
|
||||
rnsConn, err := bgrpc.ClientSetup(c.WFE.GetNonceService, tlsConfig, clientMetrics, clk)
|
||||
cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to get nonce service")
|
||||
rns = noncepb.NewNonceServiceClient(rnsConn)
|
||||
for prefix, serviceConfig := range c.WFE.RedeemNonceServices {
|
||||
conn, err := bgrpc.ClientSetup(&serviceConfig, tlsConfig, clientMetrics, clk)
|
||||
cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to redeem nonce service")
|
||||
npm[prefix] = noncepb.NewNonceServiceClient(conn)
|
||||
}
|
||||
}
|
||||
|
||||
return rac, sac, rns
|
||||
return rac, sac, rns, npm
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
@ -230,8 +244,8 @@ func main() {
|
|||
|
||||
kp, err := goodkey.NewKeyPolicy("") // don't load any weak keys
|
||||
cmd.FailOnError(err, "Unable to create key policy")
|
||||
rac, sac, rns := setupWFE(c, logger, scope, clk)
|
||||
wfe, err := wfe2.NewWebFrontEndImpl(scope, clk, kp, certChains, rns, logger)
|
||||
rac, sac, rns, npm := setupWFE(c, logger, scope, clk)
|
||||
wfe, err := wfe2.NewWebFrontEndImpl(scope, clk, kp, certChains, rns, npm, logger)
|
||||
cmd.FailOnError(err, "Unable to create WFE")
|
||||
wfe.RA = rac
|
||||
wfe.SA = sac
|
||||
|
|
|
@ -3,13 +3,10 @@ package main
|
|||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/letsencrypt/boulder/cmd"
|
||||
corepb "github.com/letsencrypt/boulder/core/proto"
|
||||
bgrpc "github.com/letsencrypt/boulder/grpc"
|
||||
blog "github.com/letsencrypt/boulder/log"
|
||||
"github.com/letsencrypt/boulder/nonce"
|
||||
noncepb "github.com/letsencrypt/boulder/nonce/proto"
|
||||
)
|
||||
|
@ -17,68 +14,18 @@ import (
|
|||
type config struct {
|
||||
NonceService struct {
|
||||
cmd.ServiceConfig
|
||||
Syslog cmd.SyslogConfig
|
||||
MaxUsed int
|
||||
|
||||
RemoteNonceServices []cmd.GRPCClientConfig
|
||||
Syslog cmd.SyslogConfig
|
||||
MaxUsed int
|
||||
NoncePrefix string
|
||||
}
|
||||
}
|
||||
|
||||
type nonceServer struct {
|
||||
inner *nonce.NonceService
|
||||
remoteServices []noncepb.NonceServiceClient
|
||||
log blog.Logger
|
||||
}
|
||||
|
||||
func (ns *nonceServer) remoteRedeem(ctx context.Context, msg *noncepb.NonceMessage) bool {
|
||||
deadline, ok := ctx.Deadline()
|
||||
if !ok {
|
||||
ns.log.Err("Context passed to remoteRedeem does not have a deadline")
|
||||
return false
|
||||
}
|
||||
subCtx, cancel := context.WithDeadline(ctx, deadline.Add(-time.Millisecond*250))
|
||||
defer cancel()
|
||||
msg.Forwarded = true
|
||||
results := make(chan bool, len(ns.remoteServices))
|
||||
wg := new(sync.WaitGroup)
|
||||
for _, remote := range ns.remoteServices {
|
||||
wg.Add(1)
|
||||
go func(r noncepb.NonceServiceClient) {
|
||||
defer wg.Done()
|
||||
resp, err := r.Redeem(subCtx, msg)
|
||||
if err != nil {
|
||||
ns.log.Errf("remote Redeem call failed: %s", err)
|
||||
return
|
||||
}
|
||||
results <- resp.Valid
|
||||
}(remote)
|
||||
}
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(results)
|
||||
}()
|
||||
for result := range results {
|
||||
select {
|
||||
case <-subCtx.Done():
|
||||
return false
|
||||
default:
|
||||
if result {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
inner *nonce.NonceService
|
||||
}
|
||||
|
||||
func (ns *nonceServer) Redeem(ctx context.Context, msg *noncepb.NonceMessage) (*noncepb.ValidMessage, error) {
|
||||
valid := ns.inner.Valid(msg.Nonce)
|
||||
// If the nonce was not valid, we have configured remote nonce services,
|
||||
// and this Redeem message wasn't forwarded, then forward it to the
|
||||
// remote services
|
||||
if !valid && len(ns.remoteServices) > 0 && !msg.Forwarded {
|
||||
valid = ns.remoteRedeem(ctx, msg)
|
||||
}
|
||||
return &noncepb.ValidMessage{Valid: valid}, nil
|
||||
return &noncepb.ValidMessage{Valid: ns.inner.Valid(msg.Nonce)}, nil
|
||||
}
|
||||
|
||||
func (ns *nonceServer) Nonce(_ context.Context, _ *corepb.Empty) (*noncepb.NonceMessage, error) {
|
||||
|
@ -92,6 +39,7 @@ func (ns *nonceServer) Nonce(_ context.Context, _ *corepb.Empty) (*noncepb.Nonce
|
|||
func main() {
|
||||
grpcAddr := flag.String("addr", "", "gRPC listen address override")
|
||||
debugAddr := flag.String("debug-addr", "", "Debug server address override")
|
||||
prefixOverride := flag.String("prefix", "", "Override the configured nonce prefix")
|
||||
configFile := flag.String("config", "", "File path to the configuration file for this service")
|
||||
flag.Parse()
|
||||
|
||||
|
@ -105,27 +53,21 @@ func main() {
|
|||
if *debugAddr != "" {
|
||||
c.NonceService.DebugAddr = *debugAddr
|
||||
}
|
||||
if *prefixOverride != "" {
|
||||
c.NonceService.NoncePrefix = *prefixOverride
|
||||
}
|
||||
|
||||
scope, logger := cmd.StatsAndLogging(c.NonceService.Syslog, c.NonceService.DebugAddr)
|
||||
defer logger.AuditPanic()
|
||||
logger.Info(cmd.VersionString())
|
||||
|
||||
ns, err := nonce.NewNonceService(scope, c.NonceService.MaxUsed)
|
||||
ns, err := nonce.NewNonceService(scope, c.NonceService.MaxUsed, c.NonceService.NoncePrefix)
|
||||
cmd.FailOnError(err, "Failed to initialize nonce service")
|
||||
|
||||
tlsConfig, err := c.NonceService.TLS.Load()
|
||||
cmd.FailOnError(err, "tlsConfig config")
|
||||
|
||||
nonceServer := &nonceServer{inner: ns, log: logger}
|
||||
if len(c.NonceService.RemoteNonceServices) > 0 {
|
||||
clientMetrics := bgrpc.NewClientMetrics(scope)
|
||||
clk := cmd.Clock()
|
||||
for _, remoteNonceConfig := range c.NonceService.RemoteNonceServices {
|
||||
rnsConn, err := bgrpc.ClientSetup(&remoteNonceConfig, tlsConfig, clientMetrics, clk)
|
||||
cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to Nonce service")
|
||||
nonceServer.remoteServices = append(nonceServer.remoteServices, noncepb.NewNonceServiceClient(rnsConn))
|
||||
}
|
||||
}
|
||||
nonceServer := &nonceServer{inner: ns}
|
||||
|
||||
serverMetrics := bgrpc.NewServerMetrics(scope)
|
||||
grpcSrv, l, err := bgrpc.NewServer(c.NonceService.GRPC, tlsConfig, serverMetrics, cmd.Clock())
|
||||
|
|
|
@ -1,115 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
corepb "github.com/letsencrypt/boulder/core/proto"
|
||||
blog "github.com/letsencrypt/boulder/log"
|
||||
"github.com/letsencrypt/boulder/metrics"
|
||||
"github.com/letsencrypt/boulder/nonce"
|
||||
noncepb "github.com/letsencrypt/boulder/nonce/proto"
|
||||
"github.com/letsencrypt/boulder/test"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
type workingRemote struct{ resp bool }
|
||||
|
||||
func (wr *workingRemote) Redeem(ctx context.Context, in *noncepb.NonceMessage, opts ...grpc.CallOption) (*noncepb.ValidMessage, error) {
|
||||
return &noncepb.ValidMessage{
|
||||
Valid: wr.resp,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (wr *workingRemote) Nonce(ctx context.Context, in *corepb.Empty, opts ...grpc.CallOption) (*noncepb.NonceMessage, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type sleepingRemote struct{}
|
||||
|
||||
func (sr *sleepingRemote) Redeem(ctx context.Context, in *noncepb.NonceMessage, opts ...grpc.CallOption) (*noncepb.ValidMessage, error) {
|
||||
time.Sleep(time.Millisecond * 50)
|
||||
return &noncepb.ValidMessage{
|
||||
Valid: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (sr *sleepingRemote) Nonce(ctx context.Context, in *corepb.Empty, opts ...grpc.CallOption) (*noncepb.NonceMessage, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type brokenRemote struct{}
|
||||
|
||||
func (br *brokenRemote) Redeem(ctx context.Context, in *noncepb.NonceMessage, opts ...grpc.CallOption) (*noncepb.ValidMessage, error) {
|
||||
return nil, errors.New("BROKE!")
|
||||
}
|
||||
|
||||
func (br *brokenRemote) Nonce(ctx context.Context, in *corepb.Empty, opts ...grpc.CallOption) (*noncepb.NonceMessage, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func TestRemoteRedeem(t *testing.T) {
|
||||
l := blog.NewMock()
|
||||
|
||||
innerNs, err := nonce.NewNonceService(metrics.NewNoopScope(), 1)
|
||||
test.AssertNotError(t, err, "NewNonceService failed")
|
||||
ns := nonceServer{log: l, inner: innerNs}
|
||||
|
||||
// Working remote returning valid nonce message
|
||||
ns.remoteServices = []noncepb.NonceServiceClient{
|
||||
&workingRemote{resp: false},
|
||||
&workingRemote{resp: true},
|
||||
}
|
||||
nonce := "asd"
|
||||
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(time.Hour))
|
||||
resp, err := ns.Redeem(ctx, &noncepb.NonceMessage{Nonce: nonce, Forwarded: false})
|
||||
cancel()
|
||||
test.AssertNotError(t, err, "Redeem failed")
|
||||
test.Assert(t, resp.Valid, "Redeem returned the wrong response")
|
||||
|
||||
// Working remotes returning invalid nonce message
|
||||
ns.remoteServices = []noncepb.NonceServiceClient{
|
||||
&workingRemote{resp: false},
|
||||
&workingRemote{resp: false},
|
||||
}
|
||||
ctx, cancel = context.WithDeadline(context.Background(), time.Now().Add(time.Hour))
|
||||
resp, err = ns.Redeem(ctx, &noncepb.NonceMessage{Nonce: nonce, Forwarded: false})
|
||||
cancel()
|
||||
test.AssertNotError(t, err, "Redeem failed")
|
||||
test.Assert(t, !resp.Valid, "Redeem returned the wrong response")
|
||||
|
||||
// Sleeping remotes returns valid nonce message, but after 50ms, Redeem should return false
|
||||
ns.remoteServices = []noncepb.NonceServiceClient{
|
||||
&sleepingRemote{},
|
||||
&sleepingRemote{},
|
||||
}
|
||||
ctx, cancel = context.WithDeadline(context.Background(), time.Now().Add(time.Millisecond))
|
||||
resp, err = ns.Redeem(ctx, &noncepb.NonceMessage{Nonce: nonce, Forwarded: false})
|
||||
cancel()
|
||||
test.AssertNotError(t, err, "Redeem failed")
|
||||
test.Assert(t, !resp.Valid, "Redeem returned the wrong response")
|
||||
|
||||
// Already forwarded message, Redeem should return false
|
||||
ns.remoteServices = []noncepb.NonceServiceClient{
|
||||
&workingRemote{resp: true},
|
||||
&workingRemote{resp: true},
|
||||
}
|
||||
ctx, cancel = context.WithDeadline(context.Background(), time.Now().Add(time.Hour))
|
||||
resp, err = ns.Redeem(ctx, &noncepb.NonceMessage{Nonce: nonce, Forwarded: true})
|
||||
cancel()
|
||||
test.AssertNotError(t, err, "Redeem failed")
|
||||
test.Assert(t, !resp.Valid, "Redeem returned the wrong response")
|
||||
|
||||
// Broken remotes, Redeem should return false
|
||||
ns.remoteServices = []noncepb.NonceServiceClient{
|
||||
&brokenRemote{},
|
||||
&brokenRemote{},
|
||||
}
|
||||
ctx, cancel = context.WithDeadline(context.Background(), time.Now().Add(time.Millisecond))
|
||||
resp, err = ns.Redeem(ctx, &noncepb.NonceMessage{Nonce: nonce, Forwarded: false})
|
||||
cancel()
|
||||
test.AssertNotError(t, err, "Redeem failed")
|
||||
test.Assert(t, !resp.Valid, "Redeem returned the wrong response")
|
||||
}
|
|
@ -23,6 +23,7 @@ services:
|
|||
- publisher1.boulder
|
||||
- ocsp-updater.boulder
|
||||
- admin-revoker.boulder
|
||||
- nonce1.boulder
|
||||
rednet:
|
||||
ipv4_address: 10.88.88.88
|
||||
aliases:
|
||||
|
@ -31,6 +32,7 @@ services:
|
|||
- ra2.boulder
|
||||
- va2.boulder
|
||||
- publisher2.boulder
|
||||
- nonce2.boulder
|
||||
# Use sd-test-srv as a backup to Docker's embedded DNS server
|
||||
# (https://docs.docker.com/config/containers/container-networking/#dns-services).
|
||||
# If there's a name Docker's DNS server doesn't know about, it will
|
||||
|
|
|
@ -15,16 +15,19 @@ package nonce
|
|||
|
||||
import (
|
||||
"container/heap"
|
||||
"context"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/letsencrypt/boulder/metrics"
|
||||
noncepb "github.com/letsencrypt/boulder/nonce/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -43,6 +46,7 @@ type NonceService struct {
|
|||
usedHeap *int64Heap
|
||||
gcm cipher.AEAD
|
||||
maxUsed int
|
||||
prefix string
|
||||
stats metrics.Scope
|
||||
}
|
||||
|
||||
|
@ -65,8 +69,24 @@ func (h *int64Heap) Pop() interface{} {
|
|||
}
|
||||
|
||||
// NewNonceService constructs a NonceService with defaults
|
||||
func NewNonceService(scope metrics.Scope, maxUsed int) (*NonceService, error) {
|
||||
func NewNonceService(scope metrics.Scope, maxUsed int, prefix string) (*NonceService, error) {
|
||||
scope = scope.NewScope("NonceService")
|
||||
|
||||
// If a prefix is provided it must be four characters and valid
|
||||
// base64. The prefix is required to be base64url as RFC8555
|
||||
// section 6.5.1 requires that nonces use that encoding.
|
||||
// As base64 operates on three byte binary segments we require
|
||||
// the prefix to be three bytes (four characters) so that the
|
||||
// bytes preceding the prefix wouldn't impact the encoding.
|
||||
if prefix != "" {
|
||||
if len(prefix) != 4 {
|
||||
return nil, errors.New("nonce prefix must be 4 characters")
|
||||
}
|
||||
if _, err := base64.RawURLEncoding.DecodeString(prefix); err != nil {
|
||||
return nil, errors.New("nonce prefix must be valid base64url")
|
||||
}
|
||||
}
|
||||
|
||||
key := make([]byte, 16)
|
||||
if _, err := rand.Read(key); err != nil {
|
||||
return nil, err
|
||||
|
@ -92,6 +112,7 @@ func NewNonceService(scope metrics.Scope, maxUsed int) (*NonceService, error) {
|
|||
usedHeap: &int64Heap{},
|
||||
gcm: gcm,
|
||||
maxUsed: maxUsed,
|
||||
prefix: prefix,
|
||||
stats: scope,
|
||||
}, nil
|
||||
}
|
||||
|
@ -117,11 +138,24 @@ func (ns *NonceService) encrypt(counter int64) (string, error) {
|
|||
ct := ns.gcm.Seal(nil, nonce, pt, nil)
|
||||
copy(ret, nonce[4:])
|
||||
copy(ret[8:], ct)
|
||||
return base64.RawURLEncoding.EncodeToString(ret), nil
|
||||
|
||||
return ns.prefix + base64.RawURLEncoding.EncodeToString(ret), nil
|
||||
}
|
||||
|
||||
func (ns *NonceService) decrypt(nonce string) (int64, error) {
|
||||
decoded, err := base64.RawURLEncoding.DecodeString(nonce)
|
||||
body := nonce
|
||||
if ns.prefix != "" {
|
||||
var prefix string
|
||||
var err error
|
||||
prefix, body, err = splitNonce(nonce)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if ns.prefix != prefix {
|
||||
return 0, fmt.Errorf("nonce contains invalid prefix: expected %q, got %q", ns.prefix, prefix)
|
||||
}
|
||||
}
|
||||
decoded, err := base64.RawURLEncoding.DecodeString(body)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
@ -193,3 +227,28 @@ func (ns *NonceService) Valid(nonce string) bool {
|
|||
ns.stats.Inc("Valid", 1)
|
||||
return true
|
||||
}
|
||||
|
||||
func splitNonce(nonce string) (string, string, error) {
|
||||
if len(nonce) < 4 {
|
||||
return "", "", errInvalidNonceLength
|
||||
}
|
||||
return nonce[:4], nonce[4:], nil
|
||||
}
|
||||
|
||||
// RemoteRedeem checks the nonce prefix and routes the Redeem RPC
|
||||
// to the associated remote nonce service
|
||||
func RemoteRedeem(ctx context.Context, noncePrefixMap map[string]noncepb.NonceServiceClient, nonce string) (bool, error) {
|
||||
prefix, _, err := splitNonce(nonce)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
nonceService, present := noncePrefixMap[prefix]
|
||||
if !present {
|
||||
return false, nil
|
||||
}
|
||||
resp, err := nonceService.Redeem(ctx, &noncepb.NonceMessage{Nonce: nonce})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return resp.Valid, nil
|
||||
}
|
||||
|
|
|
@ -1,15 +1,20 @@
|
|||
package nonce
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
corepb "github.com/letsencrypt/boulder/core/proto"
|
||||
"github.com/letsencrypt/boulder/metrics"
|
||||
noncepb "github.com/letsencrypt/boulder/nonce/proto"
|
||||
"github.com/letsencrypt/boulder/test"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func TestValidNonce(t *testing.T) {
|
||||
ns, err := NewNonceService(metrics.NewNoopScope(), 0)
|
||||
ns, err := NewNonceService(metrics.NewNoopScope(), 0, "")
|
||||
test.AssertNotError(t, err, "Could not create nonce service")
|
||||
n, err := ns.Nonce()
|
||||
test.AssertNotError(t, err, "Could not create nonce")
|
||||
|
@ -17,7 +22,7 @@ func TestValidNonce(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestAlreadyUsed(t *testing.T) {
|
||||
ns, err := NewNonceService(metrics.NewNoopScope(), 0)
|
||||
ns, err := NewNonceService(metrics.NewNoopScope(), 0, "")
|
||||
test.AssertNotError(t, err, "Could not create nonce service")
|
||||
n, err := ns.Nonce()
|
||||
test.AssertNotError(t, err, "Could not create nonce")
|
||||
|
@ -26,7 +31,7 @@ func TestAlreadyUsed(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRejectMalformed(t *testing.T) {
|
||||
ns, err := NewNonceService(metrics.NewNoopScope(), 0)
|
||||
ns, err := NewNonceService(metrics.NewNoopScope(), 0, "")
|
||||
test.AssertNotError(t, err, "Could not create nonce service")
|
||||
n, err := ns.Nonce()
|
||||
test.AssertNotError(t, err, "Could not create nonce")
|
||||
|
@ -34,15 +39,15 @@ func TestRejectMalformed(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRejectShort(t *testing.T) {
|
||||
ns, err := NewNonceService(metrics.NewNoopScope(), 0)
|
||||
ns, err := NewNonceService(metrics.NewNoopScope(), 0, "")
|
||||
test.AssertNotError(t, err, "Could not create nonce service")
|
||||
test.Assert(t, !ns.Valid("aGkK"), "Accepted an invalid nonce")
|
||||
}
|
||||
|
||||
func TestRejectUnknown(t *testing.T) {
|
||||
ns1, err := NewNonceService(metrics.NewNoopScope(), 0)
|
||||
ns1, err := NewNonceService(metrics.NewNoopScope(), 0, "")
|
||||
test.AssertNotError(t, err, "Could not create nonce service")
|
||||
ns2, err := NewNonceService(metrics.NewNoopScope(), 0)
|
||||
ns2, err := NewNonceService(metrics.NewNoopScope(), 0, "")
|
||||
test.AssertNotError(t, err, "Could not create nonce service")
|
||||
|
||||
n, err := ns1.Nonce()
|
||||
|
@ -51,7 +56,7 @@ func TestRejectUnknown(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRejectTooLate(t *testing.T) {
|
||||
ns, err := NewNonceService(metrics.NewNoopScope(), 0)
|
||||
ns, err := NewNonceService(metrics.NewNoopScope(), 0, "")
|
||||
test.AssertNotError(t, err, "Could not create nonce service")
|
||||
|
||||
ns.latest = 2
|
||||
|
@ -62,7 +67,7 @@ func TestRejectTooLate(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRejectTooEarly(t *testing.T) {
|
||||
ns, err := NewNonceService(metrics.NewNoopScope(), 0)
|
||||
ns, err := NewNonceService(metrics.NewNoopScope(), 0, "")
|
||||
test.AssertNotError(t, err, "Could not create nonce service")
|
||||
|
||||
n0, err := ns.Nonce()
|
||||
|
@ -90,7 +95,7 @@ func TestRejectTooEarly(t *testing.T) {
|
|||
}
|
||||
|
||||
func BenchmarkNonces(b *testing.B) {
|
||||
ns, err := NewNonceService(metrics.NewNoopScope(), 0)
|
||||
ns, err := NewNonceService(metrics.NewNoopScope(), 0, "")
|
||||
if err != nil {
|
||||
b.Fatal("creating nonce service", err)
|
||||
}
|
||||
|
@ -118,3 +123,90 @@ func BenchmarkNonces(b *testing.B) {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestNoncePrefixing(t *testing.T) {
|
||||
ns, err := NewNonceService(metrics.NewNoopScope(), 0, "zinc")
|
||||
test.AssertNotError(t, err, "Could not create nonce service")
|
||||
|
||||
n, err := ns.Nonce()
|
||||
test.AssertNotError(t, err, "Could not create nonce")
|
||||
test.Assert(t, ns.Valid(n), "Valid nonce rejected")
|
||||
|
||||
n, err = ns.Nonce()
|
||||
test.AssertNotError(t, err, "Could not create nonce")
|
||||
n = n[1:]
|
||||
test.Assert(t, !ns.Valid(n), "Valid nonce with incorrect prefix accepted")
|
||||
|
||||
n, err = ns.Nonce()
|
||||
test.AssertNotError(t, err, "Could not create nonce")
|
||||
test.Assert(t, !ns.Valid(n[6:]), "Valid nonce without prefix accepted")
|
||||
}
|
||||
|
||||
type malleableNonceClient struct {
|
||||
redeem func(ctx context.Context, in *noncepb.NonceMessage, opts ...grpc.CallOption) (*noncepb.ValidMessage, error)
|
||||
}
|
||||
|
||||
func (mnc *malleableNonceClient) Redeem(ctx context.Context, in *noncepb.NonceMessage, opts ...grpc.CallOption) (*noncepb.ValidMessage, error) {
|
||||
return mnc.redeem(ctx, in, opts...)
|
||||
}
|
||||
|
||||
func (mnc *malleableNonceClient) Nonce(ctx context.Context, in *corepb.Empty, opts ...grpc.CallOption) (*noncepb.NonceMessage, error) {
|
||||
return nil, errors.New("unimplemented")
|
||||
}
|
||||
|
||||
func TestRemoteRedeem(t *testing.T) {
|
||||
valid, err := RemoteRedeem(context.Background(), nil, "q")
|
||||
test.AssertNotError(t, err, "RemoteRedeem failed")
|
||||
test.Assert(t, !valid, "RemoteRedeem accepted an invalid nonce")
|
||||
valid, err = RemoteRedeem(context.Background(), nil, "")
|
||||
test.AssertNotError(t, err, "RemoteRedeem failed")
|
||||
test.Assert(t, !valid, "RemoteRedeem accepted an empty nonce")
|
||||
|
||||
prefixMap := map[string]noncepb.NonceServiceClient{
|
||||
"abcd": &malleableNonceClient{
|
||||
redeem: func(ctx context.Context, in *noncepb.NonceMessage, opts ...grpc.CallOption) (*noncepb.ValidMessage, error) {
|
||||
return nil, errors.New("wrong one!")
|
||||
},
|
||||
},
|
||||
"wxyz": &malleableNonceClient{
|
||||
redeem: func(ctx context.Context, in *noncepb.NonceMessage, opts ...grpc.CallOption) (*noncepb.ValidMessage, error) {
|
||||
return &noncepb.ValidMessage{Valid: false}, nil
|
||||
},
|
||||
},
|
||||
}
|
||||
// Attempt to redeem a nonce with a prefix not in the prefix map, expect return false, nil
|
||||
valid, err = RemoteRedeem(context.Background(), prefixMap, "asddCQEC")
|
||||
test.AssertNotError(t, err, "RemoteRedeem failed")
|
||||
test.Assert(t, !valid, "RemoteRedeem accepted nonce not in prefix map")
|
||||
|
||||
// Attempt to redeem a nonce with a prefix in the prefix map, remote returns error
|
||||
// expect false, err
|
||||
_, err = RemoteRedeem(context.Background(), prefixMap, "abcdbeef")
|
||||
test.AssertError(t, err, "RemoteRedeem didn't return error when remote did")
|
||||
|
||||
// Attempt to redeem a nonce with a prefix in the prefix map, remote returns valid
|
||||
// expect true, nil
|
||||
valid, err = RemoteRedeem(context.Background(), prefixMap, "wxyzdead")
|
||||
test.AssertNotError(t, err, "RemoteRedeem failed")
|
||||
test.Assert(t, !valid, "RemoteRedeem didn't honor remote result")
|
||||
|
||||
// Attempt to redeem a nonce with a prefix in the prefix map, remote returns invalid
|
||||
// expect false, nil
|
||||
prefixMap["wxyz"] = &malleableNonceClient{
|
||||
redeem: func(ctx context.Context, in *noncepb.NonceMessage, opts ...grpc.CallOption) (*noncepb.ValidMessage, error) {
|
||||
return &noncepb.ValidMessage{Valid: true}, nil
|
||||
},
|
||||
}
|
||||
valid, err = RemoteRedeem(context.Background(), prefixMap, "wxyzdead")
|
||||
test.AssertNotError(t, err, "RemoteRedeem failed")
|
||||
test.Assert(t, valid, "RemoteRedeem didn't honor remote result")
|
||||
}
|
||||
|
||||
func TestNoncePrefixValidation(t *testing.T) {
|
||||
_, err := NewNonceService(metrics.NewNoopScope(), 0, "hey")
|
||||
test.AssertError(t, err, "NewNonceService didn't fail with short prefix")
|
||||
_, err = NewNonceService(metrics.NewNoopScope(), 0, "hey!")
|
||||
test.AssertError(t, err, "NewNonceService didn't fail with invalid base64")
|
||||
_, err = NewNonceService(metrics.NewNoopScope(), 0, "heyy")
|
||||
test.AssertNotError(t, err, "NewNonceService failed with valid nonce prefix")
|
||||
}
|
||||
|
|
|
@ -27,7 +27,6 @@ const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
|
|||
|
||||
type NonceMessage struct {
|
||||
Nonce string `protobuf:"bytes,1,opt,name=nonce,proto3" json:"nonce,omitempty"`
|
||||
Forwarded bool `protobuf:"varint,2,opt,name=forwarded,proto3" json:"forwarded,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
|
@ -65,13 +64,6 @@ func (m *NonceMessage) GetNonce() string {
|
|||
return ""
|
||||
}
|
||||
|
||||
func (m *NonceMessage) GetForwarded() bool {
|
||||
if m != nil {
|
||||
return m.Forwarded
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type ValidMessage struct {
|
||||
Valid bool `protobuf:"varint,1,opt,name=valid,proto3" json:"valid,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
|
@ -119,20 +111,18 @@ func init() {
|
|||
func init() { proto.RegisterFile("nonce/proto/nonce.proto", fileDescriptor_9197b76ef104b424) }
|
||||
|
||||
var fileDescriptor_9197b76ef104b424 = []byte{
|
||||
// 193 bytes of a gzipped FileDescriptorProto
|
||||
// 169 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0xcf, 0xcb, 0xcf, 0x4b,
|
||||
0x4e, 0xd5, 0x2f, 0x28, 0xca, 0x2f, 0xc9, 0xd7, 0x07, 0xb3, 0xf5, 0xc0, 0x6c, 0x21, 0x56, 0x30,
|
||||
0x47, 0x4a, 0x34, 0x39, 0xbf, 0x08, 0x26, 0x0d, 0x62, 0x42, 0x64, 0x95, 0x9c, 0xb8, 0x78, 0xfc,
|
||||
0x47, 0x4a, 0x34, 0x39, 0xbf, 0x08, 0x26, 0x0d, 0x62, 0x42, 0x64, 0x95, 0x54, 0xb8, 0x78, 0xfc,
|
||||
0x40, 0xf2, 0xbe, 0xa9, 0xc5, 0xc5, 0x89, 0xe9, 0xa9, 0x42, 0x22, 0x5c, 0x10, 0xf5, 0x12, 0x8c,
|
||||
0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0x10, 0x8e, 0x90, 0x0c, 0x17, 0x67, 0x5a, 0x7e, 0x51, 0x79, 0x62,
|
||||
0x51, 0x4a, 0x6a, 0x8a, 0x04, 0x93, 0x02, 0xa3, 0x06, 0x47, 0x10, 0x42, 0x40, 0x49, 0x85, 0x8b,
|
||||
0x27, 0x2c, 0x31, 0x27, 0x33, 0x05, 0xc9, 0x8c, 0x32, 0x10, 0x1f, 0x6c, 0x06, 0x47, 0x10, 0x84,
|
||||
0x63, 0x54, 0x08, 0xb5, 0x29, 0x38, 0xb5, 0xa8, 0x2c, 0x33, 0x39, 0x55, 0x48, 0x9b, 0x8b, 0x15,
|
||||
0xcc, 0x17, 0xe2, 0xd6, 0x03, 0xbb, 0xc7, 0x35, 0xb7, 0xa0, 0xa4, 0x52, 0x4a, 0x58, 0x0f, 0xe2,
|
||||
0x76, 0x64, 0x47, 0x29, 0x31, 0x08, 0x99, 0x70, 0xb1, 0x05, 0xa5, 0xa6, 0xa4, 0xa6, 0xe6, 0x0a,
|
||||
0x61, 0x53, 0x00, 0xd7, 0x85, 0xec, 0x0c, 0x25, 0x06, 0x27, 0xf6, 0x28, 0x56, 0xb0, 0x2f, 0x93,
|
||||
0xd8, 0xc0, 0x94, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0xd2, 0x89, 0x71, 0x61, 0x25, 0x01, 0x00,
|
||||
0x00,
|
||||
0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0x10, 0x0e, 0x48, 0x55, 0x58, 0x62, 0x4e, 0x66, 0x0a, 0x92, 0xaa,
|
||||
0x32, 0x10, 0x1f, 0xac, 0x8a, 0x23, 0x08, 0xc2, 0x31, 0x2a, 0x84, 0x9a, 0x15, 0x9c, 0x5a, 0x54,
|
||||
0x96, 0x99, 0x9c, 0x2a, 0xa4, 0xcd, 0xc5, 0x0a, 0xe6, 0x0b, 0x71, 0xeb, 0x81, 0x6d, 0x74, 0xcd,
|
||||
0x2d, 0x28, 0xa9, 0x94, 0x12, 0xd6, 0x83, 0xb8, 0x0e, 0xd9, 0x5a, 0x25, 0x06, 0x21, 0x13, 0x2e,
|
||||
0xb6, 0xa0, 0xd4, 0x94, 0xd4, 0xd4, 0x5c, 0x21, 0x6c, 0x0a, 0xe0, 0xba, 0x90, 0x9d, 0xa1, 0xc4,
|
||||
0xe0, 0xc4, 0x1e, 0xc5, 0x0a, 0xf6, 0x47, 0x12, 0x1b, 0x98, 0x32, 0x06, 0x04, 0x00, 0x00, 0xff,
|
||||
0xff, 0x9a, 0xba, 0xd9, 0x37, 0x07, 0x01, 0x00, 0x00,
|
||||
}
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
|
|
|
@ -12,7 +12,6 @@ service NonceService {
|
|||
|
||||
message NonceMessage {
|
||||
string nonce = 1;
|
||||
bool forwarded = 2;
|
||||
}
|
||||
|
||||
message ValidMessage {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"NonceService": {
|
||||
"maxUsed": 131072,
|
||||
"noncePrefix": "taro",
|
||||
"syslog": {
|
||||
"stdoutLevel": 6
|
||||
},
|
||||
|
@ -8,24 +9,13 @@
|
|||
"grpc": {
|
||||
"address": ":9101",
|
||||
"clientNames": [
|
||||
"wfe.boulder",
|
||||
"nonce.boulder"
|
||||
"wfe.boulder"
|
||||
]
|
||||
},
|
||||
"tls": {
|
||||
"caCertFile": "test/grpc-creds/minica.pem",
|
||||
"certFile": "test/grpc-creds/nonce.boulder/cert.pem",
|
||||
"keyFile": "test/grpc-creds/nonce.boulder/key.pem"
|
||||
},
|
||||
"remoteNonceServices": [
|
||||
{
|
||||
"serverAddress": "nonce.boulder:9102",
|
||||
"timeout": "15s"
|
||||
},
|
||||
{
|
||||
"serverAddress": "nonce.boulder:9101",
|
||||
"timeout": "15s"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,10 +30,20 @@
|
|||
"serverAddress": "sa.boulder:9095",
|
||||
"timeout": "15s"
|
||||
},
|
||||
"nonceService": {
|
||||
"getNonceService": {
|
||||
"serverAddress": "nonce.boulder:9101",
|
||||
"timeout": "15s"
|
||||
},
|
||||
"redeemNonceServices": {
|
||||
"taro": {
|
||||
"serverAddress": "nonce1.boulder:9101",
|
||||
"timeout": "15s"
|
||||
},
|
||||
"zinc": {
|
||||
"serverAddress": "nonce2.boulder:9101",
|
||||
"timeout": "15s"
|
||||
}
|
||||
},
|
||||
"features": {
|
||||
"NewAuthorizationSchema": true
|
||||
}
|
||||
|
|
|
@ -31,10 +31,20 @@
|
|||
"serverAddress": "sa.boulder:9095",
|
||||
"timeout": "15s"
|
||||
},
|
||||
"nonceService": {
|
||||
"getNonceService": {
|
||||
"serverAddress": "nonce.boulder:9101",
|
||||
"timeout": "15s"
|
||||
},
|
||||
"redeemNonceServices": {
|
||||
"taro": {
|
||||
"serverAddress": "nonce1.boulder:9101",
|
||||
"timeout": "15s"
|
||||
},
|
||||
"zinc": {
|
||||
"serverAddress": "nonce2.boulder:9101",
|
||||
"timeout": "15s"
|
||||
}
|
||||
},
|
||||
"certificateChains": {
|
||||
"http://boulder:4430/acme/issuer-cert": [ "test/test-ca2.pem" ],
|
||||
"http://127.0.0.1:4000/acme/issuer-cert": [ "test/test-ca2.pem" ]
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"NonceService": {
|
||||
"maxUsed": 131072,
|
||||
"noncePrefix": "taro",
|
||||
"syslog": {
|
||||
"stdoutLevel": 6
|
||||
},
|
||||
|
|
|
@ -87,8 +87,8 @@ def start(race_detection, fakeclock=None, config_dir=default_config_dir):
|
|||
[8006, './bin/ocsp-updater --config %s' % os.path.join(config_dir, "ocsp-updater.json")],
|
||||
[8002, './bin/boulder-ra --config %s --addr ra1.boulder:9094 --debug-addr :8002' % os.path.join(config_dir, "ra.json")],
|
||||
[8102, './bin/boulder-ra --config %s --addr ra2.boulder:9094 --debug-addr :8102' % os.path.join(config_dir, "ra.json")],
|
||||
[8111, './bin/nonce-service --config %s' % os.path.join(config_dir, "nonce.json")],
|
||||
[8112, './bin/nonce-service --config %s --addr nonce.boulder:9102 --debug-addr :8112' % os.path.join(config_dir, "nonce.json")],
|
||||
[8111, './bin/nonce-service --config %s --addr nonce1.boulder:9101 --debug-addr :8111 --prefix taro' % os.path.join(config_dir, "nonce.json")],
|
||||
[8112, './bin/nonce-service --config %s --addr nonce2.boulder:9101 --debug-addr :8112 --prefix zinc' % os.path.join(config_dir, "nonce.json")],
|
||||
[4431, './bin/boulder-wfe2 --config %s' % os.path.join(config_dir, "wfe2.json")],
|
||||
[4000, './bin/boulder-wfe --config %s' % os.path.join(config_dir, "wfe.json")],
|
||||
])
|
||||
|
|
17
wfe/wfe.go
17
wfe/wfe.go
|
@ -94,6 +94,7 @@ type WebFrontEndImpl struct {
|
|||
// Register of anti-replay nonces
|
||||
nonceService *nonce.NonceService
|
||||
remoteNonceService noncepb.NonceServiceClient
|
||||
noncePrefixMap map[string]noncepb.NonceServiceClient
|
||||
|
||||
// Key policy.
|
||||
keyPolicy goodkey.KeyPolicy
|
||||
|
@ -116,6 +117,7 @@ func NewWebFrontEndImpl(
|
|||
clk clock.Clock,
|
||||
keyPolicy goodkey.KeyPolicy,
|
||||
remoteNonceService noncepb.NonceServiceClient,
|
||||
noncePrefixMap map[string]noncepb.NonceServiceClient,
|
||||
logger blog.Logger,
|
||||
) (WebFrontEndImpl, error) {
|
||||
csrSignatureAlgs := prometheus.NewCounterVec(
|
||||
|
@ -134,10 +136,11 @@ func NewWebFrontEndImpl(
|
|||
keyPolicy: keyPolicy,
|
||||
csrSignatureAlgs: csrSignatureAlgs,
|
||||
remoteNonceService: remoteNonceService,
|
||||
noncePrefixMap: noncePrefixMap,
|
||||
}
|
||||
|
||||
if wfe.remoteNonceService == nil {
|
||||
nonceService, err := nonce.NewNonceService(stats, 0)
|
||||
nonceService, err := nonce.NewNonceService(stats, 0, "")
|
||||
if err != nil {
|
||||
return WebFrontEndImpl{}, err
|
||||
}
|
||||
|
@ -561,24 +564,24 @@ func (wfe *WebFrontEndImpl) verifyPOST(ctx context.Context, logEvent *web.Reques
|
|||
logEvent.Payload = string(payload)
|
||||
|
||||
// Check that the request has a known anti-replay nonce
|
||||
nonce := parsedJws.Signatures[0].Header.Nonce
|
||||
if len(nonce) == 0 {
|
||||
nonceStr := parsedJws.Signatures[0].Header.Nonce
|
||||
if len(nonceStr) == 0 {
|
||||
wfe.stats.Inc("Errors.JWSMissingNonce", 1)
|
||||
return nil, nil, reg, probs.BadNonce("JWS has no anti-replay nonce")
|
||||
}
|
||||
var nonceValid bool
|
||||
if wfe.remoteNonceService != nil {
|
||||
validMsg, err := wfe.remoteNonceService.Redeem(ctx, &noncepb.NonceMessage{Nonce: nonce})
|
||||
valid, err := nonce.RemoteRedeem(ctx, wfe.noncePrefixMap, nonceStr)
|
||||
if err != nil {
|
||||
return nil, nil, reg, probs.ServerInternal("failed to verify nonce validity: %s", err)
|
||||
}
|
||||
nonceValid = validMsg.Valid
|
||||
nonceValid = valid
|
||||
} else {
|
||||
nonceValid = wfe.nonceService.Valid(nonce)
|
||||
nonceValid = wfe.nonceService.Valid(nonceStr)
|
||||
}
|
||||
if !nonceValid {
|
||||
wfe.stats.Inc("Errors.JWSInvalidNonce", 1)
|
||||
return nil, nil, reg, probs.BadNonce("JWS has invalid anti-replay nonce %s", nonce)
|
||||
return nil, nil, reg, probs.BadNonce("JWS has invalid anti-replay nonce %s", nonceStr)
|
||||
}
|
||||
|
||||
// Check that the "resource" field is present and has the correct value
|
||||
|
|
|
@ -380,7 +380,7 @@ func setupWFE(t *testing.T) (WebFrontEndImpl, clock.FakeClock) {
|
|||
fc := clock.NewFake()
|
||||
stats := metrics.NewNoopScope()
|
||||
|
||||
wfe, err := NewWebFrontEndImpl(stats, fc, testKeyPolicy, nil, blog.NewMock())
|
||||
wfe, err := NewWebFrontEndImpl(stats, fc, testKeyPolicy, nil, nil, blog.NewMock())
|
||||
test.AssertNotError(t, err, "Unable to create WFE")
|
||||
|
||||
wfe.SubscriberAgreementURL = agreementURL
|
||||
|
|
|
@ -19,7 +19,7 @@ import (
|
|||
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
berrors "github.com/letsencrypt/boulder/errors"
|
||||
noncepb "github.com/letsencrypt/boulder/nonce/proto"
|
||||
"github.com/letsencrypt/boulder/nonce"
|
||||
"github.com/letsencrypt/boulder/probs"
|
||||
"github.com/letsencrypt/boulder/web"
|
||||
)
|
||||
|
@ -182,24 +182,23 @@ func (wfe *WebFrontEndImpl) validNonce(ctx context.Context, jws *jose.JSONWebSig
|
|||
// validNonce is called after validPOSTRequest() and parseJWS() which
|
||||
// defend against the incorrect number of signatures.
|
||||
header := jws.Signatures[0].Header
|
||||
nonce := header.Nonce
|
||||
if len(nonce) == 0 {
|
||||
if len(header.Nonce) == 0 {
|
||||
wfe.stats.joseErrorCount.With(prometheus.Labels{"type": "JWSMissingNonce"}).Inc()
|
||||
return probs.BadNonce("JWS has no anti-replay nonce")
|
||||
}
|
||||
var nonceValid bool
|
||||
if wfe.remoteNonceService != nil {
|
||||
validMsg, err := wfe.remoteNonceService.Redeem(ctx, &noncepb.NonceMessage{Nonce: nonce})
|
||||
valid, err := nonce.RemoteRedeem(ctx, wfe.noncePrefixMap, header.Nonce)
|
||||
if err != nil {
|
||||
return probs.ServerInternal("failed to verify nonce validity: %s", err)
|
||||
}
|
||||
nonceValid = validMsg.Valid
|
||||
nonceValid = valid
|
||||
} else {
|
||||
nonceValid = wfe.nonceService.Valid(nonce)
|
||||
nonceValid = wfe.nonceService.Valid(header.Nonce)
|
||||
}
|
||||
if !nonceValid {
|
||||
wfe.stats.joseErrorCount.With(prometheus.Labels{"type": "JWSInvalidNonce"}).Inc()
|
||||
return probs.BadNonce("JWS has an invalid anti-replay nonce: %q", nonce)
|
||||
return probs.BadNonce("JWS has an invalid anti-replay nonce: %q", header.Nonce)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -101,6 +101,7 @@ type WebFrontEndImpl struct {
|
|||
// Register of anti-replay nonces
|
||||
nonceService *nonce.NonceService
|
||||
remoteNonceService noncepb.NonceServiceClient
|
||||
noncePrefixMap map[string]noncepb.NonceServiceClient
|
||||
|
||||
// Key policy.
|
||||
keyPolicy goodkey.KeyPolicy
|
||||
|
@ -122,6 +123,7 @@ func NewWebFrontEndImpl(
|
|||
keyPolicy goodkey.KeyPolicy,
|
||||
certificateChains map[string][]byte,
|
||||
remoteNonceService noncepb.NonceServiceClient,
|
||||
noncePrefixMap map[string]noncepb.NonceServiceClient,
|
||||
logger blog.Logger,
|
||||
) (WebFrontEndImpl, error) {
|
||||
wfe := WebFrontEndImpl{
|
||||
|
@ -132,10 +134,11 @@ func NewWebFrontEndImpl(
|
|||
stats: initStats(scope),
|
||||
scope: scope,
|
||||
remoteNonceService: remoteNonceService,
|
||||
noncePrefixMap: noncePrefixMap,
|
||||
}
|
||||
|
||||
if wfe.remoteNonceService == nil {
|
||||
nonceService, err := nonce.NewNonceService(scope, 0)
|
||||
nonceService, err := nonce.NewNonceService(scope, 0, "")
|
||||
if err != nil {
|
||||
return WebFrontEndImpl{}, err
|
||||
}
|
||||
|
|
|
@ -357,7 +357,7 @@ func setupWFE(t *testing.T) (WebFrontEndImpl, clock.FakeClock) {
|
|||
"http://localhost:4000/acme/issuer-cert": append([]byte{'\n'}, chainPEM...),
|
||||
}
|
||||
|
||||
wfe, err := NewWebFrontEndImpl(stats, fc, testKeyPolicy, certChains, nil, blog.NewMock())
|
||||
wfe, err := NewWebFrontEndImpl(stats, fc, testKeyPolicy, certChains, nil, nil, blog.NewMock())
|
||||
test.AssertNotError(t, err, "Unable to create WFE")
|
||||
|
||||
wfe.SubscriberAgreementURL = agreementURL
|
||||
|
|
Loading…
Reference in New Issue