Duplicate WFE to WFE2. (#2839)
This PR is the initial duplication of the WFE to create a WFE2 package. The rationale is briefly explained in `wfe2/README.md`. Per #2822 this PR only lays the groundwork for further customization and deduplication. Presently both the WFE and WFE2 are identical except for the following configuration differences: * The WFE offers HTTP and HTTPS on 4000 and 4430 respectively, the WFE2 offers HTTP on 4001 and 4431. * The WFE has a debug port on 8000, the WFE2 uses the next free "8000 range port" and puts its debug service on 8013 Resolves https://github.com/letsencrypt/boulder/issues/2822
This commit is contained in:
parent
7120d72197
commit
bd3e2747ba
|
|
@ -2,8 +2,8 @@
|
|||
# be used as the base of the bhsm container in boulder/docker-compose.yml
|
||||
FROM letsencrypt/boulder-tools:2017-05-25
|
||||
|
||||
# Boulder exposes its web application at port TCP 4000
|
||||
EXPOSE 4000 4002 4003 4430 8053 8055
|
||||
# Boulder exposes its web application at port TCP 4000 and 4001
|
||||
EXPOSE 4000 4001 4002 4003 4430 4431 8053 8055
|
||||
|
||||
ENV PATH /usr/local/go/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin/
|
||||
ENV GOPATH /go
|
||||
|
|
|
|||
|
|
@ -0,0 +1,181 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"flag"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/facebookgo/httpdown"
|
||||
"github.com/jmhodges/clock"
|
||||
|
||||
"github.com/letsencrypt/boulder/cmd"
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
"github.com/letsencrypt/boulder/features"
|
||||
"github.com/letsencrypt/boulder/goodkey"
|
||||
bgrpc "github.com/letsencrypt/boulder/grpc"
|
||||
blog "github.com/letsencrypt/boulder/log"
|
||||
"github.com/letsencrypt/boulder/metrics"
|
||||
rapb "github.com/letsencrypt/boulder/ra/proto"
|
||||
sapb "github.com/letsencrypt/boulder/sa/proto"
|
||||
"github.com/letsencrypt/boulder/wfe2"
|
||||
)
|
||||
|
||||
const clientName = "WFE2"
|
||||
|
||||
type config struct {
|
||||
WFE struct {
|
||||
cmd.ServiceConfig
|
||||
BaseURL string
|
||||
ListenAddress string
|
||||
TLSListenAddress string
|
||||
|
||||
ServerCertificatePath string
|
||||
ServerKeyPath string
|
||||
|
||||
AllowOrigins []string
|
||||
|
||||
CertCacheDuration cmd.ConfigDuration
|
||||
CertNoCacheExpirationWindow cmd.ConfigDuration
|
||||
IndexCacheDuration cmd.ConfigDuration
|
||||
IssuerCacheDuration cmd.ConfigDuration
|
||||
|
||||
ShutdownStopTimeout cmd.ConfigDuration
|
||||
ShutdownKillTimeout cmd.ConfigDuration
|
||||
|
||||
SubscriberAgreementURL string
|
||||
|
||||
AcceptRevocationReason bool
|
||||
AllowAuthzDeactivation bool
|
||||
|
||||
TLS cmd.TLSConfig
|
||||
|
||||
RAService *cmd.GRPCClientConfig
|
||||
SAService *cmd.GRPCClientConfig
|
||||
|
||||
Features map[string]bool
|
||||
}
|
||||
|
||||
SubscriberAgreementURL string
|
||||
|
||||
Syslog cmd.SyslogConfig
|
||||
|
||||
Common struct {
|
||||
BaseURL string
|
||||
IssuerCert string
|
||||
}
|
||||
}
|
||||
|
||||
func setupWFE(c config, logger blog.Logger, stats metrics.Scope) (core.RegistrationAuthority, core.StorageAuthority) {
|
||||
var tls *tls.Config
|
||||
var err error
|
||||
if c.WFE.TLS.CertFile != nil {
|
||||
tls, err = c.WFE.TLS.Load()
|
||||
cmd.FailOnError(err, "TLS config")
|
||||
}
|
||||
|
||||
raConn, err := bgrpc.ClientSetup(c.WFE.RAService, tls, stats)
|
||||
cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to RA")
|
||||
rac := bgrpc.NewRegistrationAuthorityClient(rapb.NewRegistrationAuthorityClient(raConn))
|
||||
|
||||
saConn, err := bgrpc.ClientSetup(c.WFE.SAService, tls, stats)
|
||||
cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to SA")
|
||||
sac := bgrpc.NewStorageAuthorityClient(sapb.NewStorageAuthorityClient(saConn))
|
||||
|
||||
return rac, sac
|
||||
}
|
||||
|
||||
func main() {
|
||||
configFile := flag.String("config", "", "File path to the configuration file for this service")
|
||||
flag.Parse()
|
||||
if *configFile == "" {
|
||||
flag.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var c config
|
||||
err := cmd.ReadConfigFile(*configFile, &c)
|
||||
cmd.FailOnError(err, "Reading JSON config file into config structure")
|
||||
|
||||
err = features.Set(c.WFE.Features)
|
||||
cmd.FailOnError(err, "Failed to set feature flags")
|
||||
|
||||
scope, logger := cmd.StatsAndLogging(c.Syslog)
|
||||
defer logger.AuditPanic()
|
||||
logger.Info(cmd.VersionString(clientName))
|
||||
|
||||
kp, err := goodkey.NewKeyPolicy("") // don't load any weak keys
|
||||
cmd.FailOnError(err, "Unable to create key policy")
|
||||
wfe, err := wfe2.NewWebFrontEndImpl(scope, clock.Default(), kp, logger)
|
||||
cmd.FailOnError(err, "Unable to create WFE")
|
||||
rac, sac := setupWFE(c, logger, scope)
|
||||
wfe.RA = rac
|
||||
wfe.SA = sac
|
||||
|
||||
// TODO: remove this check once the production config uses the SubscriberAgreementURL in the wfe section
|
||||
if c.WFE.SubscriberAgreementURL != "" {
|
||||
wfe.SubscriberAgreementURL = c.WFE.SubscriberAgreementURL
|
||||
} else {
|
||||
wfe.SubscriberAgreementURL = c.SubscriberAgreementURL
|
||||
}
|
||||
|
||||
wfe.AllowOrigins = c.WFE.AllowOrigins
|
||||
wfe.AcceptRevocationReason = c.WFE.AcceptRevocationReason
|
||||
wfe.AllowAuthzDeactivation = c.WFE.AllowAuthzDeactivation
|
||||
|
||||
wfe.CertCacheDuration = c.WFE.CertCacheDuration.Duration
|
||||
wfe.CertNoCacheExpirationWindow = c.WFE.CertNoCacheExpirationWindow.Duration
|
||||
wfe.IndexCacheDuration = c.WFE.IndexCacheDuration.Duration
|
||||
wfe.IssuerCacheDuration = c.WFE.IssuerCacheDuration.Duration
|
||||
|
||||
wfe.IssuerCert, err = cmd.LoadCert(c.Common.IssuerCert)
|
||||
cmd.FailOnError(err, fmt.Sprintf("Couldn't read issuer cert [%s]", c.Common.IssuerCert))
|
||||
|
||||
logger.Info(fmt.Sprintf("WFE using key policy: %#v", kp))
|
||||
|
||||
// Set up paths
|
||||
wfe.BaseURL = c.Common.BaseURL
|
||||
|
||||
logger.Info(fmt.Sprintf("Server running, listening on %s...\n", c.WFE.ListenAddress))
|
||||
srv := &http.Server{
|
||||
Addr: c.WFE.ListenAddress,
|
||||
Handler: wfe.Handler(),
|
||||
}
|
||||
|
||||
go cmd.DebugServer(c.WFE.DebugAddr)
|
||||
go cmd.ProfileCmd(scope)
|
||||
|
||||
hd := &httpdown.HTTP{
|
||||
StopTimeout: c.WFE.ShutdownStopTimeout.Duration,
|
||||
KillTimeout: c.WFE.ShutdownKillTimeout.Duration,
|
||||
}
|
||||
hdSrv, err := hd.ListenAndServe(srv)
|
||||
cmd.FailOnError(err, "Error starting HTTP server")
|
||||
|
||||
var hdTLSSrv httpdown.Server
|
||||
if c.WFE.TLSListenAddress != "" {
|
||||
cer, err := tls.LoadX509KeyPair(c.WFE.ServerCertificatePath, c.WFE.ServerKeyPath)
|
||||
cmd.FailOnError(err, "Couldn't read WFE server certificate or key")
|
||||
tlsConfig := &tls.Config{Certificates: []tls.Certificate{cer}}
|
||||
|
||||
logger.Info(fmt.Sprintf("TLS Server running, listening on %s...\n", c.WFE.TLSListenAddress))
|
||||
TLSSrv := &http.Server{
|
||||
Addr: c.WFE.TLSListenAddress,
|
||||
Handler: wfe.Handler(),
|
||||
TLSConfig: tlsConfig,
|
||||
}
|
||||
hdTLSSrv, err = hd.ListenAndServe(TLSSrv)
|
||||
cmd.FailOnError(err, "Error starting TLS server")
|
||||
}
|
||||
|
||||
go cmd.CatchSignals(logger, func() {
|
||||
_ = hdSrv.Stop()
|
||||
if hdTLSSrv != nil {
|
||||
_ = hdTLSSrv.Stop()
|
||||
}
|
||||
})
|
||||
|
||||
forever := make(chan struct{}, 1)
|
||||
<-forever
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
package main
|
||||
|
|
@ -17,9 +17,11 @@ services:
|
|||
- boulder:127.0.0.1
|
||||
ports:
|
||||
- 4000:4000 # ACME
|
||||
- 4001:4001 # ACMEv2
|
||||
- 4002:4002 # OCSP
|
||||
- 4003:4003 # OCSP
|
||||
- 4430:4430 # ACME via HTTPS
|
||||
- 4431:4431 # ACMEv2 via HTTPS
|
||||
- 4500:4500 # ct-test-srv
|
||||
- 6000:6000 # gsb-test-srv
|
||||
- 8000:8000 # debug ports
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
{
|
||||
"wfe": {
|
||||
"listenAddress": "0.0.0.0:4001",
|
||||
"TLSListenAddress": "0.0.0.0:4431",
|
||||
"serverCertificatePath": "test/wfe.pem",
|
||||
"serverKeyPath": "test/wfe.key",
|
||||
"requestTimeout": "10s",
|
||||
"allowOrigins": ["*"],
|
||||
"certCacheDuration": "6h",
|
||||
"certNoCacheExpirationWindow": "96h",
|
||||
"indexCacheDuration": "24h",
|
||||
"issuerCacheDuration": "48h",
|
||||
"shutdownStopTimeout": "10s",
|
||||
"shutdownKillTimeout": "1m",
|
||||
"subscriberAgreementURL": "http://boulder:4001/terms/v1",
|
||||
"acceptRevocationReason": true,
|
||||
"allowAuthzDeactivation": true,
|
||||
"debugAddr": ":8013",
|
||||
"tls": {
|
||||
"caCertFile": "test/grpc-creds/minica.pem",
|
||||
"certFile": "test/grpc-creds/wfe.boulder/cert.pem",
|
||||
"keyFile": "test/grpc-creds/wfe.boulder/key.pem"
|
||||
},
|
||||
"raService": {
|
||||
"serverAddresses": ["ra.boulder:19094"],
|
||||
"timeout": "15s"
|
||||
},
|
||||
"saService": {
|
||||
"serverAddresses": ["sa.boulder:19095"],
|
||||
"timeout": "15s"
|
||||
},
|
||||
"features": {
|
||||
"AllowAccountDeactivation": true,
|
||||
"AllowKeyRollover": true,
|
||||
"UseAIAIssuerURL": true,
|
||||
"RandomDirectoryEntry": true,
|
||||
"DirectoryMeta": true
|
||||
}
|
||||
},
|
||||
|
||||
"syslog": {
|
||||
"stdoutlevel": 6,
|
||||
"sysloglevel": 4
|
||||
},
|
||||
|
||||
"common": {
|
||||
"issuerCert": "test/test-ca.pem",
|
||||
"dnsResolver": "127.0.0.1:8053"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
{
|
||||
"wfe": {
|
||||
"listenAddress": "0.0.0.0:4001",
|
||||
"TLSListenAddress": "0.0.0.0:4431",
|
||||
"serverCertificatePath": "test/wfe.pem",
|
||||
"serverKeyPath": "test/wfe.key",
|
||||
"requestTimeout": "10s",
|
||||
"allowOrigins": ["*"],
|
||||
"certCacheDuration": "6h",
|
||||
"certNoCacheExpirationWindow": "96h",
|
||||
"indexCacheDuration": "24h",
|
||||
"issuerCacheDuration": "48h",
|
||||
"shutdownStopTimeout": "10s",
|
||||
"shutdownKillTimeout": "1m",
|
||||
"subscriberAgreementURL": "http://boulder:4001/terms/v1",
|
||||
"checkMalformedCSR": true,
|
||||
"allowAuthzDeactivation": true,
|
||||
"debugAddr": ":8013",
|
||||
"tls": {
|
||||
"caCertFile": "test/grpc-creds/minica.pem",
|
||||
"certFile": "test/grpc-creds/wfe.boulder/cert.pem",
|
||||
"keyFile": "test/grpc-creds/wfe.boulder/key.pem"
|
||||
},
|
||||
"raService": {
|
||||
"serverAddresses": ["ra.boulder:19094"],
|
||||
"timeout": "15s"
|
||||
},
|
||||
"saService": {
|
||||
"serverAddresses": ["sa.boulder:19095"],
|
||||
"timeout": "15s"
|
||||
}
|
||||
},
|
||||
"allowedSigningAlgos": {
|
||||
"rsa": true,
|
||||
"ecdsanistp256": true,
|
||||
"ecdsanistp384": true,
|
||||
"ecdsanistp521": false
|
||||
},
|
||||
|
||||
"statsd": {
|
||||
"server": "localhost:8125",
|
||||
"prefix": "Boulder"
|
||||
},
|
||||
|
||||
"subscriberAgreementURL": "http://boulder:4001/terms/v1",
|
||||
|
||||
"syslog": {
|
||||
"stdoutlevel": 6
|
||||
},
|
||||
|
||||
"common": {
|
||||
"issuerCert": "test/test-ca.pem"
|
||||
}
|
||||
}
|
||||
|
|
@ -65,9 +65,11 @@ fi
|
|||
docker run --rm -it \
|
||||
"${fake_dns_args[@]}" \
|
||||
-p 4000:4000 \
|
||||
-p 4001:4001 \
|
||||
-p 4002:4002 \
|
||||
-p 4003:4003 \
|
||||
-p 4430:4430 \
|
||||
-p 4431:4431 \
|
||||
-p 8053:8053 \
|
||||
-p 8055:8055 \
|
||||
--name boulder \
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ def start(race_detection):
|
|||
'gsb-test-srv -apikey my-voice-is-my-passport',
|
||||
'boulder-sa --config %s' % os.path.join(default_config_dir, "sa.json"),
|
||||
'boulder-wfe --config %s' % os.path.join(default_config_dir, "wfe.json"),
|
||||
'boulder-wfe2 --config %s' % os.path.join(default_config_dir, "wfe2.json"),
|
||||
'boulder-ra --config %s' % os.path.join(default_config_dir, "ra.json"),
|
||||
'boulder-ca --config %s' % os.path.join(default_config_dir, "ca.json"),
|
||||
'boulder-va --config %s' % os.path.join(default_config_dir, "va.json"),
|
||||
|
|
@ -100,10 +101,12 @@ def start(race_detection):
|
|||
# If one of the servers has died, quit immediately.
|
||||
if not check():
|
||||
return False
|
||||
ports = range(8000, 8005) + [4000, 4430]
|
||||
ports = range(8000, 8005) + [4000, 4001, 4430, 4431]
|
||||
if default_config_dir.startswith("test/config-next"):
|
||||
# Add the two 'remote' VA debug ports
|
||||
ports.extend([8011, 8012])
|
||||
# Add the wfe v2 debug port
|
||||
ports.extend([8013])
|
||||
for debug_port in ports:
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.connect(('localhost', debug_port))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
WFE v2
|
||||
============
|
||||
|
||||
The `wfe2` package is copied from the `wfe` package in order to implement the
|
||||
["ACME v2"](https://letsencrypt.org/2017/06/14/acme-v2-api.html) API. This design choice
|
||||
was made to facilitate a clean separation between v1 and v2 code and to support
|
||||
running a separate API process on a different port alongside the v1 API process.
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
package wfe2
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/jmhodges/clock"
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
blog "github.com/letsencrypt/boulder/log"
|
||||
)
|
||||
|
||||
type requestEvent struct {
|
||||
ID string `json:",omitempty"`
|
||||
RealIP string `json:",omitempty"`
|
||||
Endpoint string `json:",omitempty"`
|
||||
Method string `json:",omitempty"`
|
||||
Errors []string `json:",omitempty"`
|
||||
Requester int64 `json:",omitempty"`
|
||||
Contacts *[]string `json:",omitempty"`
|
||||
RequestNonce string `json:",omitempty"`
|
||||
ResponseNonce string `json:",omitempty"`
|
||||
UserAgent string `json:",omitempty"`
|
||||
Code int
|
||||
Payload string `json:",omitempty"`
|
||||
Extra map[string]interface{} `json:",omitempty"`
|
||||
}
|
||||
|
||||
func (e *requestEvent) AddError(msg string, args ...interface{}) {
|
||||
e.Errors = append(e.Errors, fmt.Sprintf(msg, args...))
|
||||
}
|
||||
|
||||
type wfeHandlerFunc func(context.Context, *requestEvent, http.ResponseWriter, *http.Request)
|
||||
|
||||
func (f wfeHandlerFunc) ServeHTTP(e *requestEvent, w http.ResponseWriter, r *http.Request) {
|
||||
ctx := context.TODO()
|
||||
f(ctx, e, w, r)
|
||||
}
|
||||
|
||||
type wfeHandler interface {
|
||||
ServeHTTP(e *requestEvent, w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
|
||||
type topHandler struct {
|
||||
wfe wfeHandler
|
||||
log blog.Logger
|
||||
clk clock.Clock
|
||||
}
|
||||
|
||||
func (th *topHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
logEvent := &requestEvent{
|
||||
ID: core.NewToken(),
|
||||
RealIP: r.Header.Get("X-Real-IP"),
|
||||
Method: r.Method,
|
||||
UserAgent: r.Header.Get("User-Agent"),
|
||||
Extra: make(map[string]interface{}, 0),
|
||||
}
|
||||
w.Header().Set("Boulder-Request-ID", logEvent.ID)
|
||||
defer th.logEvent(logEvent)
|
||||
|
||||
th.wfe.ServeHTTP(logEvent, w, r)
|
||||
}
|
||||
|
||||
func (th *topHandler) logEvent(logEvent *requestEvent) {
|
||||
var msg string
|
||||
if len(logEvent.Errors) != 0 {
|
||||
msg = "Terminated request"
|
||||
} else {
|
||||
msg = "Successful request"
|
||||
}
|
||||
jsonEvent, err := json.Marshal(logEvent)
|
||||
if err != nil {
|
||||
th.log.AuditErr(fmt.Sprintf("%s - failed to marshal logEvent - %s", msg, err))
|
||||
return
|
||||
}
|
||||
th.log.Info(fmt.Sprintf("%s JSON=%s", msg, jsonEvent))
|
||||
}
|
||||
|
||||
// Comma-separated list of HTTP clients involved in making this
|
||||
// request, starting with the original requestor and ending with the
|
||||
// remote end of our TCP connection (which is typically our own
|
||||
// proxy).
|
||||
func getClientAddr(r *http.Request) string {
|
||||
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
|
||||
return xff + "," + r.RemoteAddr
|
||||
}
|
||||
return r.RemoteAddr
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
package wfe2
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/rsa"
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/square/go-jose.v1"
|
||||
)
|
||||
|
||||
func algorithmForKey(key *jose.JsonWebKey) (string, error) {
|
||||
switch k := key.Key.(type) {
|
||||
case *rsa.PublicKey:
|
||||
return string(jose.RS256), nil
|
||||
case *ecdsa.PublicKey:
|
||||
switch k.Params().Name {
|
||||
case "P-256":
|
||||
return string(jose.ES256), nil
|
||||
case "P-384":
|
||||
return string(jose.ES384), nil
|
||||
case "P-521":
|
||||
return string(jose.ES512), nil
|
||||
}
|
||||
}
|
||||
return "", signatureValidationError("no signature algorithms suitable for given key type")
|
||||
}
|
||||
|
||||
const (
|
||||
noAlgorithmForKey = "WFE.Errors.NoAlgorithmForKey"
|
||||
invalidJWSAlgorithm = "WFE.Errors.InvalidJWSAlgorithm"
|
||||
invalidAlgorithmOnKey = "WFE.Errors.InvalidAlgorithmOnKey"
|
||||
)
|
||||
|
||||
// Check that (1) there is a suitable algorithm for the provided key based on its
|
||||
// Golang type, (2) the Algorithm field on the JWK is either absent, or matches
|
||||
// that algorithm, and (3) the Algorithm field on the JWK is present and matches
|
||||
// that algorithm. Precondition: parsedJws must have exactly one signature on
|
||||
// it. Returns stat name to increment if err is non-nil.
|
||||
func checkAlgorithm(key *jose.JsonWebKey, parsedJws *jose.JsonWebSignature) (string, error) {
|
||||
algorithm, err := algorithmForKey(key)
|
||||
if err != nil {
|
||||
return noAlgorithmForKey, err
|
||||
}
|
||||
jwsAlgorithm := parsedJws.Signatures[0].Header.Algorithm
|
||||
if jwsAlgorithm != algorithm {
|
||||
return invalidJWSAlgorithm, signatureValidationError(fmt.Sprintf(
|
||||
"signature type '%s' in JWS header is not supported, expected one of RS256, ES256, ES384 or ES512",
|
||||
jwsAlgorithm,
|
||||
))
|
||||
}
|
||||
if key.Algorithm != "" && key.Algorithm != algorithm {
|
||||
return invalidAlgorithmOnKey, signatureValidationError(fmt.Sprintf(
|
||||
"algorithm '%s' on JWK is unacceptable",
|
||||
key.Algorithm,
|
||||
))
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
|
@ -0,0 +1,203 @@
|
|||
package wfe2
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rsa"
|
||||
"testing"
|
||||
|
||||
"gopkg.in/square/go-jose.v1"
|
||||
)
|
||||
|
||||
func TestRejectsNone(t *testing.T) {
|
||||
wfe, _ := setupWFE(t)
|
||||
_, _, _, prob := wfe.verifyPOST(ctx, newRequestEvent(), makePostRequest(`
|
||||
{
|
||||
"header": {
|
||||
"alg": "none",
|
||||
"jwk": {
|
||||
"kty": "RSA",
|
||||
"n": "vrjT",
|
||||
"e": "AQAB"
|
||||
}
|
||||
},
|
||||
"payload": "aGkK",
|
||||
"signature": ""
|
||||
}
|
||||
`), true, "foo")
|
||||
if prob == nil {
|
||||
t.Fatalf("verifyPOST did not reject JWS with alg: 'none'")
|
||||
}
|
||||
if prob.Detail != "signature type 'none' in JWS header is not supported, expected one of RS256, ES256, ES384 or ES512" {
|
||||
t.Fatalf("verifyPOST rejected JWS with alg: 'none', but for wrong reason: %#v", prob)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRejectsHS256(t *testing.T) {
|
||||
wfe, _ := setupWFE(t)
|
||||
_, _, _, prob := wfe.verifyPOST(ctx, newRequestEvent(), makePostRequest(`
|
||||
{
|
||||
"header": {
|
||||
"alg": "HS256",
|
||||
"jwk": {
|
||||
"kty": "RSA",
|
||||
"n": "vrjT",
|
||||
"e": "AQAB"
|
||||
}
|
||||
},
|
||||
"payload": "aGkK",
|
||||
"signature": ""
|
||||
}
|
||||
`), true, "foo")
|
||||
if prob == nil {
|
||||
t.Fatalf("verifyPOST did not reject JWS with alg: 'HS256'")
|
||||
}
|
||||
expected := "signature type 'HS256' in JWS header is not supported, expected one of RS256, ES256, ES384 or ES512"
|
||||
if prob.Detail != expected {
|
||||
t.Fatalf("verifyPOST rejected JWS with alg: 'none', but for wrong reason: got '%s', wanted %s", prob, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckAlgorithm(t *testing.T) {
|
||||
testCases := []struct {
|
||||
key jose.JsonWebKey
|
||||
jws jose.JsonWebSignature
|
||||
expectedErr string
|
||||
expectedStat string
|
||||
}{
|
||||
{
|
||||
jose.JsonWebKey{
|
||||
Algorithm: "HS256",
|
||||
},
|
||||
jose.JsonWebSignature{},
|
||||
"no signature algorithms suitable for given key type",
|
||||
"WFE.Errors.NoAlgorithmForKey",
|
||||
},
|
||||
{
|
||||
jose.JsonWebKey{
|
||||
Key: &rsa.PublicKey{},
|
||||
},
|
||||
jose.JsonWebSignature{
|
||||
Signatures: []jose.Signature{
|
||||
{
|
||||
Header: jose.JoseHeader{
|
||||
Algorithm: "HS256",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"signature type 'HS256' in JWS header is not supported, expected one of RS256, ES256, ES384 or ES512",
|
||||
"WFE.Errors.InvalidJWSAlgorithm",
|
||||
},
|
||||
{
|
||||
jose.JsonWebKey{
|
||||
Algorithm: "HS256",
|
||||
Key: &rsa.PublicKey{},
|
||||
},
|
||||
jose.JsonWebSignature{
|
||||
Signatures: []jose.Signature{
|
||||
{
|
||||
Header: jose.JoseHeader{
|
||||
Algorithm: "HS256",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"signature type 'HS256' in JWS header is not supported, expected one of RS256, ES256, ES384 or ES512",
|
||||
"WFE.Errors.InvalidJWSAlgorithm",
|
||||
},
|
||||
{
|
||||
jose.JsonWebKey{
|
||||
Algorithm: "HS256",
|
||||
Key: &rsa.PublicKey{},
|
||||
},
|
||||
jose.JsonWebSignature{
|
||||
Signatures: []jose.Signature{
|
||||
{
|
||||
Header: jose.JoseHeader{
|
||||
Algorithm: "RS256",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"algorithm 'HS256' on JWK is unacceptable",
|
||||
"WFE.Errors.InvalidAlgorithmOnKey",
|
||||
},
|
||||
}
|
||||
for i, tc := range testCases {
|
||||
stat, err := checkAlgorithm(&tc.key, &tc.jws)
|
||||
if tc.expectedErr != "" && err.Error() != tc.expectedErr {
|
||||
t.Errorf("TestCheckAlgorithm %d: Expected '%s', got '%s'", i, tc.expectedErr, err)
|
||||
}
|
||||
if tc.expectedStat != "" && stat != tc.expectedStat {
|
||||
t.Errorf("TestCheckAlgorithm %d: Expected stat '%s', got '%s'", i, tc.expectedStat, stat)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckAlgorithmSuccess(t *testing.T) {
|
||||
_, err := checkAlgorithm(&jose.JsonWebKey{
|
||||
Algorithm: "RS256",
|
||||
Key: &rsa.PublicKey{},
|
||||
}, &jose.JsonWebSignature{
|
||||
Signatures: []jose.Signature{
|
||||
{
|
||||
Header: jose.JoseHeader{
|
||||
Algorithm: "RS256",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("RS256 key: Expected nil error, got '%s'", err)
|
||||
}
|
||||
_, err = checkAlgorithm(&jose.JsonWebKey{
|
||||
Key: &rsa.PublicKey{},
|
||||
}, &jose.JsonWebSignature{
|
||||
Signatures: []jose.Signature{
|
||||
{
|
||||
Header: jose.JoseHeader{
|
||||
Algorithm: "RS256",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("RS256 key: Expected nil error, got '%s'", err)
|
||||
}
|
||||
|
||||
_, err = checkAlgorithm(&jose.JsonWebKey{
|
||||
Algorithm: "ES256",
|
||||
Key: &ecdsa.PublicKey{
|
||||
Curve: elliptic.P256(),
|
||||
},
|
||||
}, &jose.JsonWebSignature{
|
||||
Signatures: []jose.Signature{
|
||||
{
|
||||
Header: jose.JoseHeader{
|
||||
Algorithm: "ES256",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("ES256 key: Expected nil error, got '%s'", err)
|
||||
}
|
||||
|
||||
_, err = checkAlgorithm(&jose.JsonWebKey{
|
||||
Key: &ecdsa.PublicKey{
|
||||
Curve: elliptic.P256(),
|
||||
},
|
||||
}, &jose.JsonWebSignature{
|
||||
Signatures: []jose.Signature{
|
||||
{
|
||||
Header: jose.JoseHeader{
|
||||
Algorithm: "ES256",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("ES256 key: Expected nil error, got '%s'", err)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
package wfe2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
berrors "github.com/letsencrypt/boulder/errors"
|
||||
"github.com/letsencrypt/boulder/probs"
|
||||
)
|
||||
|
||||
func problemDetailsForBoulderError(err *berrors.BoulderError, msg string) *probs.ProblemDetails {
|
||||
switch err.Type {
|
||||
case berrors.NotSupported:
|
||||
return &probs.ProblemDetails{
|
||||
Type: probs.ServerInternalProblem,
|
||||
Detail: fmt.Sprintf("%s :: %s", msg, err),
|
||||
HTTPStatus: http.StatusNotImplemented,
|
||||
}
|
||||
case berrors.Malformed:
|
||||
return probs.Malformed(fmt.Sprintf("%s :: %s", msg, err))
|
||||
case berrors.Unauthorized:
|
||||
return probs.Unauthorized(fmt.Sprintf("%s :: %s", msg, err))
|
||||
case berrors.NotFound:
|
||||
return probs.NotFound(fmt.Sprintf("%s :: %s", msg, err))
|
||||
case berrors.RateLimit:
|
||||
return probs.RateLimited(fmt.Sprintf("%s :: %s", msg, err))
|
||||
case berrors.InternalServer:
|
||||
// Internal server error messages may include sensitive data, so we do
|
||||
// not include it.
|
||||
return probs.ServerInternal(msg)
|
||||
case berrors.RejectedIdentifier:
|
||||
return probs.RejectedIdentifier(fmt.Sprintf("%s :: %s", msg, err))
|
||||
case berrors.InvalidEmail:
|
||||
return probs.InvalidEmail(fmt.Sprintf("%s :: %s", msg, err))
|
||||
default:
|
||||
// Internal server error messages may include sensitive data, so we do
|
||||
// not include it.
|
||||
return probs.ServerInternal(msg)
|
||||
}
|
||||
}
|
||||
|
||||
// problemDetailsForError turns an error into a ProblemDetails with the special
|
||||
// case of returning the same error back if its already a ProblemDetails. If the
|
||||
// error is of an type unknown to ProblemDetailsForError, it will return a
|
||||
// ServerInternal ProblemDetails.
|
||||
func problemDetailsForError(err error, msg string) *probs.ProblemDetails {
|
||||
switch e := err.(type) {
|
||||
case *probs.ProblemDetails:
|
||||
return e
|
||||
case *berrors.BoulderError:
|
||||
return problemDetailsForBoulderError(e, msg)
|
||||
case signatureValidationError:
|
||||
return probs.Malformed(fmt.Sprintf("%s :: %s", msg, err))
|
||||
case core.MalformedRequestError:
|
||||
return probs.Malformed(fmt.Sprintf("%s :: %s", msg, err))
|
||||
case core.NotSupportedError:
|
||||
return &probs.ProblemDetails{
|
||||
Type: probs.ServerInternalProblem,
|
||||
Detail: fmt.Sprintf("%s :: %s", msg, err),
|
||||
HTTPStatus: http.StatusNotImplemented,
|
||||
}
|
||||
case core.UnauthorizedError:
|
||||
return probs.Unauthorized(fmt.Sprintf("%s :: %s", msg, err))
|
||||
case core.NotFoundError:
|
||||
return probs.NotFound(fmt.Sprintf("%s :: %s", msg, err))
|
||||
case core.LengthRequiredError:
|
||||
prob := probs.Malformed("missing Content-Length header")
|
||||
prob.HTTPStatus = http.StatusLengthRequired
|
||||
return prob
|
||||
case core.RateLimitedError:
|
||||
return probs.RateLimited(fmt.Sprintf("%s :: %s", msg, err))
|
||||
case core.BadNonceError:
|
||||
return probs.BadNonce(fmt.Sprintf("%s :: %s", msg, err))
|
||||
default:
|
||||
// Internal server error messages may include sensitive data, so we do
|
||||
// not include it.
|
||||
return probs.ServerInternal(msg)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
package wfe2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
berrors "github.com/letsencrypt/boulder/errors"
|
||||
"github.com/letsencrypt/boulder/probs"
|
||||
"github.com/letsencrypt/boulder/test"
|
||||
)
|
||||
|
||||
func TestProblemDetailsFromError(t *testing.T) {
|
||||
// errMsg is used as the msg argument for `problemDetailsForError` and is
|
||||
// always returned in the problem detail.
|
||||
const errMsg = "testError"
|
||||
// detailMsg is used as the msg argument for the individual error types and is
|
||||
// sometimes not present in the produced problem's detail.
|
||||
const detailMsg = "testDetail"
|
||||
// fullDetail is what we expect the problem detail to look like when it
|
||||
// contains both the error message and the detail message
|
||||
fullDetail := fmt.Sprintf("%s :: %s", errMsg, detailMsg)
|
||||
testCases := []struct {
|
||||
err error
|
||||
statusCode int
|
||||
problem probs.ProblemType
|
||||
detail string
|
||||
}{
|
||||
// boulder/core error types:
|
||||
// Internal server errors expect just the `errMsg` in detail.
|
||||
{core.InternalServerError(detailMsg), 500, probs.ServerInternalProblem, errMsg},
|
||||
// Other errors expect the full detail message
|
||||
{core.NotSupportedError(detailMsg), 501, probs.ServerInternalProblem, fullDetail},
|
||||
{core.MalformedRequestError(detailMsg), 400, probs.MalformedProblem, fullDetail},
|
||||
{core.UnauthorizedError(detailMsg), 403, probs.UnauthorizedProblem, fullDetail},
|
||||
{core.NotFoundError(detailMsg), 404, probs.MalformedProblem, fullDetail},
|
||||
{signatureValidationError(detailMsg), 400, probs.MalformedProblem, fullDetail},
|
||||
{core.RateLimitedError(detailMsg), 429, probs.RateLimitedProblem, fullDetail},
|
||||
{core.BadNonceError(detailMsg), 400, probs.BadNonceProblem, fullDetail},
|
||||
// The content length error has its own specific detail message
|
||||
{core.LengthRequiredError(detailMsg), 411, probs.MalformedProblem, "missing Content-Length header"},
|
||||
// boulder/errors error types
|
||||
// Internal server errors expect just the `errMsg` in detail.
|
||||
{berrors.InternalServerError(detailMsg), 500, probs.ServerInternalProblem, errMsg},
|
||||
// Other errors expect the full detail message
|
||||
{berrors.NotSupportedError(detailMsg), 501, probs.ServerInternalProblem, fullDetail},
|
||||
{berrors.MalformedError(detailMsg), 400, probs.MalformedProblem, fullDetail},
|
||||
{berrors.UnauthorizedError(detailMsg), 403, probs.UnauthorizedProblem, fullDetail},
|
||||
{berrors.NotFoundError(detailMsg), 404, probs.MalformedProblem, fullDetail},
|
||||
{berrors.RateLimitError(detailMsg), 429, probs.RateLimitedProblem, fullDetail},
|
||||
{berrors.InvalidEmailError(detailMsg), 400, probs.InvalidEmailProblem, fullDetail},
|
||||
{berrors.RejectedIdentifierError(detailMsg), 400, probs.RejectedIdentifierProblem, fullDetail},
|
||||
}
|
||||
for _, c := range testCases {
|
||||
p := problemDetailsForError(c.err, errMsg)
|
||||
if p.HTTPStatus != c.statusCode {
|
||||
t.Errorf("Incorrect status code for %s. Expected %d, got %d", reflect.TypeOf(c.err).Name(), c.statusCode, p.HTTPStatus)
|
||||
}
|
||||
if probs.ProblemType(p.Type) != c.problem {
|
||||
t.Errorf("Expected problem urn %#v, got %#v", c.problem, p.Type)
|
||||
}
|
||||
if p.Detail != c.detail {
|
||||
t.Errorf("Expected detailed message %q, got %q", c.detail, p.Detail)
|
||||
}
|
||||
}
|
||||
|
||||
expected := &probs.ProblemDetails{
|
||||
Type: probs.MalformedProblem,
|
||||
HTTPStatus: 200,
|
||||
Detail: "gotcha",
|
||||
}
|
||||
p := problemDetailsForError(expected, "k")
|
||||
test.AssertDeepEquals(t, expected, p)
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDETCCAfmgAwIBAgICALIwDQYJKoZIhvcNAQELBQAwDjEMMAoGA1UEAxMDMTc4
|
||||
MB4XDTE3MDIwMzAzNDcyNloXDTE4MDIwMzAzNDcyNlowDjEMMAoGA1UEAxMDMTc4
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuXUn5n4NBLK6CqJXBR+t
|
||||
dM4SVY911FKAwxI13k3aQvtBjaIPe6/CDiG7ZxGDsEB96pI6yYauhDQg6ELXcPN0
|
||||
dmRX4qxVMQ/ngS7bSc7FmlN1qkq9p1AxNmesCmsWg9/4yJNCmlTdGu2Mo60Iosxx
|
||||
CnQP3faG7ZPrGwzYvX9rwNedD3GlrFarQuU8VzD91fSQIzbDBtlP/+bY4FUbDtzw
|
||||
WGpuAorrSOeDxC0Y3Tmd6IJLczof+vFP3EYjX+fwjnSWe75zz3z2DhVYu0tiid3k
|
||||
UFDLaI5pY9JYYG3/D59lVKxg48PQP5q4qqWzmFnuUW/GOFHJABFnmOoD9j4t2YLk
|
||||
GwIDAQABo3kwdzAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEw
|
||||
DAYDVR0TAQH/BAIwADBCBggrBgEFBQcBAQQ2MDQwMgYIKwYBBQUHMAKGJmh0dHA6
|
||||
Ly9sb2NhbGhvc3Q6NDAwMC9hY21lL2lzc3Vlci1jZXJ0MA0GCSqGSIb3DQEBCwUA
|
||||
A4IBAQBCeU7UGIDKqVJ3fG0GOGlz1JHDh51UIQ2w/KK3NRlqdtlQ3tcqBHYspVMz
|
||||
YjliJuiVXi/hLEd9IyaTEfxqPnpla7rYo0PgChQ/Eg+IPMJm5t3HwnuNTsvJucX+
|
||||
gCA/vGKsqSZU58JeilBVo4jl6btUc1LYMCWQ1QRfBpei/9sV0EF3f3HosqYA5I0L
|
||||
VYzmsLBd8uyttFazgQKfM7Y/h1FcWGGJkH0rsZI7h4OOl0dn2aM9SCHiergJj4Sz
|
||||
S6hUp2+RR70GSuZejYc7NGqk7/g624c6jJETEqJEBPy6tvxSq+DlVT1K3gWmM+Mc
|
||||
yJjiZCq2Lifrn0KxkKuxsqWEW2tO
|
||||
-----END CERTIFICATE-----
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpQIBAAKCAQEAuXUn5n4NBLK6CqJXBR+tdM4SVY911FKAwxI13k3aQvtBjaIP
|
||||
e6/CDiG7ZxGDsEB96pI6yYauhDQg6ELXcPN0dmRX4qxVMQ/ngS7bSc7FmlN1qkq9
|
||||
p1AxNmesCmsWg9/4yJNCmlTdGu2Mo60IosxxCnQP3faG7ZPrGwzYvX9rwNedD3Gl
|
||||
rFarQuU8VzD91fSQIzbDBtlP/+bY4FUbDtzwWGpuAorrSOeDxC0Y3Tmd6IJLczof
|
||||
+vFP3EYjX+fwjnSWe75zz3z2DhVYu0tiid3kUFDLaI5pY9JYYG3/D59lVKxg48PQ
|
||||
P5q4qqWzmFnuUW/GOFHJABFnmOoD9j4t2YLkGwIDAQABAoIBAQCZ5kfbNUU2Xd6X
|
||||
DoqqHNSDdrKuP+Om82QY/RaoyPBT309R6mdw27Rsp79tU5J1g786FmkkbViLKvsX
|
||||
4sgH2nAOA00PNLVphmo1wJ2HTUibvaCKVYW2v4xnOncBGkbP3uAECngdvEjTnMe1
|
||||
19SvzHoOE6xLJNZpdvOGOg3uizvvBVJbLg2osrJXacoulOjjpd5YCVJQT9vDKhUa
|
||||
aq7CmInYfOM2flcAo7nLHWP7Jr4FX4me50lrYuzBOaJWLHcQH/mZriTgI8cXCoJx
|
||||
fk0Lav38z6BgYumREa0OOGDVkNuxde5KSdcFUUEEfvtPSnruVwdNHja3z3d4Y1Bx
|
||||
ca1khx4JAoGBAMMFbbFRQwtxTED75dr8qXZE8kJNmIvPC1og3zRjsN0aVaUltRFl
|
||||
pj+/HZXOAxU+uc4ac7vzD/5ysSHhp2rrzVglBveSlM7U88KcEYI+Yu2KzM12UMkT
|
||||
lIWQDtfIpnvXPwnMsde9JzQvXeeyEhy6IBDwtRg3UaNdday/+V9bKUznAoGBAPNy
|
||||
NPsFtdQuT0FU8W3ehPw7dkBZl8YGy3YQxMh8IcThx4NRJHkK0yTV3/zg8owW6WvN
|
||||
EPhEIWQ4u9szf4zPoCHbckEScLeDYlc/hyf2hmQdlYQTyZXE+nou1VYawlv9mJJ1
|
||||
88Sct0ygmVcCcdCsi68abOijs+TJrGsI+cjKzTStAoGAd45vZeIMeQpXFguXKT31
|
||||
4aR44/7QAv3F1tYKIALxnUqUsK7CJ00qsy/Fwl3OdArFO73pr5Jd/r5vKvc8fIbc
|
||||
lynz8HhzM61HVsn4zeDTIw8RaPAcrHiNd6gOAWln7snRQn+zky/Jxes35V+8TNIp
|
||||
8FiwnIzlRoJ4LpRuG3A2jIcCgYEAqRroGoa4647Plv4+RqePkPZtCf4yI2iM5JJ5
|
||||
Xxp7CpwbTuiKgVo3mRrH4I0RbqZrtmpYI1yQJWITfAylyVZgUaRyFSmOCqvFH/4N
|
||||
EIF6kQjL11c3bEXMCBuILaug3u2lkfdFQYnq+duFKJ+WF/IDhbrBdEhiqcY9coxl
|
||||
lkjpinECgYEAkZO0XS0Z7KfFgSMQ2uFK8MO7naQM5kg+8H26HPuNWRmlkRDR7tOI
|
||||
gQcwSbx7vqT5JJM/bSWI49b2Q19QncFJ0A8/P3/3dncFQ8QMsfMVzmhHvcn2SthU
|
||||
Eh0aoOwi7rPYNiCTd/3y04x42a/hmo8rmcXOodZvnewOFbDu/s/m7ig=
|
||||
-----END RSA PRIVATE KEY-----
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDBzCCAe+gAwIBAgICAO4wDQYJKoZIhvcNAQELBQAwDjEMMAoGA1UEAwwDMjM4
|
||||
MB4XDTE1MDYxMzAwMTU1NVoXDTE2MDYxMjAwMTU1NVowDjEMMAoGA1UEAwwDMjM4
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvP9z1YFDa1WD9hVI9W3K
|
||||
lWQmUGfLW35x6xkgDm8o3OTWR2QoxjXratacKhm2VevV22QjCBvHXeHx3fxSp5w/
|
||||
p4CH+Ul76wCq3+WAPidO42YCP7SZdqYUR4GHKQ/oOyistRAKEamg4aPAbIs7l1Kn
|
||||
T5YHFdSzCWpe6F2+ceoluvKEn6vFVloXKghaeEyTDKnnJKs3/04TdtZjVM5OObvQ
|
||||
CGFlQlysDJxWahtVM93gylB8WYgyiekDAx1I3lCd3Vv0hF+x04xT3fwVRzmaKNzT
|
||||
wN+znae643Qfg2oSSLV066K2WYepgzqKwv3IUdrLbes331AMs+FbdxHanMrOU1i+
|
||||
OQIDAQABo28wbTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSOiDuzx4mEC9Io
|
||||
y+7rEdnE+eeOyDAfBgNVHSMEGDAWgBSOiDuzx4mEC9Ioy+7rEdnE+eeOyDAaBgNV
|
||||
HREEEzARgg9iYWQuZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADggEBAJQA/7+n
|
||||
S9AiB9YduVEs2TB7+62N59yACxd1y5qnmSLEeI9yXZnqQGugNxw7cl3CgFDWLNxB
|
||||
8Q3hH5B0fYh2Ydqf8lrEYNH3ilsmqCQB3mHUlYtLLnVarzSPrFgxaBrRaGsAAaVd
|
||||
neC5QCaxLFzzQI9gmyp6n7T2CATOk94vrrZJmfzpCMMRPHY7XgM15HDefXeH1+/Z
|
||||
GESSM/YAD6rdojZVLwxTuzVVRm5+6NfnFG938SYir0aqYvFd0bxrdgTl1XR3sAip
|
||||
iwuI3ku943Thbmyp/fEBUE2unvf+wbX+3Vzq52NadPcUrsNwJAR/kGdmTzcsiCIA
|
||||
UL+BLF470rQo29w=
|
||||
-----END CERTIFICATE-----
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC8/3PVgUNrVYP2
|
||||
FUj1bcqVZCZQZ8tbfnHrGSAObyjc5NZHZCjGNetq1pwqGbZV69XbZCMIG8dd4fHd
|
||||
/FKnnD+ngIf5SXvrAKrf5YA+J07jZgI/tJl2phRHgYcpD+g7KKy1EAoRqaDho8Bs
|
||||
izuXUqdPlgcV1LMJal7oXb5x6iW68oSfq8VWWhcqCFp4TJMMqeckqzf/ThN21mNU
|
||||
zk45u9AIYWVCXKwMnFZqG1Uz3eDKUHxZiDKJ6QMDHUjeUJ3dW/SEX7HTjFPd/BVH
|
||||
OZoo3NPA37Odp7rjdB+DahJItXTrorZZh6mDOorC/chR2stt6zffUAyz4Vt3Edqc
|
||||
ys5TWL45AgMBAAECggEAc1PSJCt/r2R8ZNJyNclsQCLfulrL3aXX7+TiCczM+5Xs
|
||||
J543v1Oxtv0ESDBuchm54ulE8zK4QlKYm6PX8A1JTnYBAx5TLoC2xG8wBT1JRzu9
|
||||
DZCvwJXxc/zXNDhPtqHIWahS7Jo84NNinRmNIHbAP7FF241yPsGY7mQdzTdbFKrR
|
||||
JH0l7VPCY4OG+CjxUJqoNuwkfrNh0hRh02IHU/rFlgR2Q7JP0XBwuufW1M6j7fYM
|
||||
7PGZRA+6Ry72UcaCEVuOtGlz3wLrFq6CGTGWlUehQqch+nrTri0jMSH4Bd83mLz2
|
||||
8+X0y/EONQlirbHbJxXq+mLASHrp3KCtdpCiLKcX8QKBgQDr+TNqLa7PIOhlw29A
|
||||
RftunKwEdsi9uAg3jFSpHC/jLxR4/fUiz2XZrAfHNxn7mOK72V/9pj9zshLnxeSm
|
||||
jEelEB2bABX8RhD38SUxoHoiWmqpPVOtBSXvMSQEO0F/1hGlxndHwe9mE2Zyq3eV
|
||||
9MoJVeExkCP3Bxk9tjZfj4WC9QKBgQDNCab2WjLy7T9Bfmh2RmWXckzUMphYCLpX
|
||||
CGG2O5nH2zOPAOxUpyLFDq3/WkzPnCdWOveI/LlZmkcjdslWp3tizk5kE1zgaFbO
|
||||
s+7o/cYVrU5J3+kIq563ba7/xZ7wpfkg58milUWStpjQrB0H5tSlUEoC7fJ/GjHd
|
||||
5j1raKQrtQKBgF9elSgJlIgD/cj7JqBsaET5LxCSzWjX0wJYRfMfAD+qTHTl9sf9
|
||||
2GUUAQTDwU2NKb3QCdqi8SwaQUfJFDM3qNEOZVi6vSf7TWpX3Ldk61etAUSrE4Fu
|
||||
/jjgvHS1WjCHXRSJ1LV8rPutRY98u1Uw3OLPAbedUNvK06m8VddjUwttAoGAAmca
|
||||
jciA0Ff3Zc0VbE1m419zhwkQv/daN6rhekE4jB8Fe6eHHXbX8Xc6ksN8IvKxg1Et
|
||||
lW1gvqwQKVo7Acj0qTPBt2qCrB6M5d817YULzTU6taLqGC/qrDuc0WJ/elJ3mOse
|
||||
cclOB2ocYFWkAXOzCjzmoSIotVSZQQBxt9CCHAECgYEA01w8tKVCG2ucbC1GoCl0
|
||||
t2MRmLqiRqRrn53fJ6j56fDbdLmnRAaaD1slZ0jpLk7JoDKGmNG2Rl9UXuydPaNZ
|
||||
8h1Lu+CnhG50uOF3A/OIXsBiRsAgI2ez4/Jb+lNe3l3UcPV5gyGejAiymqRigbkn
|
||||
bcixOm4jdOWV5Bpfv65AivQ=
|
||||
-----END PRIVATE KEY-----
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
Produced by:
|
||||
js test.js --agree --email jsha@newview.org --domains not-an-example.com --certFile cert.der --certKey ../../wfe/test/178.key
|
||||
openssl x509 -text -inform der -in cert.der -outform pem -out ../../wfe/test/not-an-example.com.crt
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEYzCCA0ugAwIBAgIRAP8AAAAAAAAOS09n2G6BjEYwDQYJKoZIhvcNAQELBQAw
|
||||
HzEdMBsGA1UEAwwUaGFwcHkgaGFja2VyIGZha2UgQ0EwHhcNMTUwOTA5MjI1NjAw
|
||||
WhcNMTUxMjA4MjI1NjAwWjAdMRswGQYDVQQDExJub3QtYW4tZXhhbXBsZS5jb20w
|
||||
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCaqzue57mgXEoGTZZoVkkC
|
||||
ZraebWgXI8irX2BgQB1A3iZa9onxGPMcWQMxhSuUisbEJi4UkMcVST12HX01rUwh
|
||||
j41UuBxJvI1w4wvdstssTAaa9c9tsQ5+UED2bFRL1MsyBdbmCF/+pu3i+ZIYqWgi
|
||||
KbjVBe3nlAVbo77zizwp3Y4Tp1/TBOwTAuFkHePmkNT63uPm9My/hNzsSm1o+Q51
|
||||
9Cf7ry+JQmOVgz/jIgFVGFYJ17EV3KUIpUuDShuyCFATBQspgJSN2DoXRUlQjXXk
|
||||
NTj23OxxdT/cVLcLJjytyG6e5izME2R2aCkDBWIc1a4/sRJ0R396auPXG6KhJ7o/
|
||||
AgMBAAGjggGaMIIBljAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUH
|
||||
AwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFBGkJtghSl97/bTR
|
||||
dyy0TaneZhnDMB8GA1UdIwQYMBaAFPt4TxL5YBWDLJ8XfzQZsy426kGJMGoGCCsG
|
||||
AQUFBwEBBF4wXDAmBggrBgEFBQcwAYYaaHR0cDovL2xvY2FsaG9zdDo0MDAyL29j
|
||||
c3AwMgYIKwYBBQUHMAKGJmh0dHA6Ly9sb2NhbGhvc3Q6NDAwMC9hY21lL2lzc3Vl
|
||||
ci1jZXJ0MB0GA1UdEQQWMBSCEm5vdC1hbi1leGFtcGxlLmNvbTAnBgNVHR8EIDAe
|
||||
MBygGqAYhhZodHRwOi8vZXhhbXBsZS5jb20vY3JsMGMGA1UdIARcMFowCgYGZ4EM
|
||||
AQIBMAAwTAYDKgMEMEUwIgYIKwYBBQUHAgEWFmh0dHA6Ly9leGFtcGxlLmNvbS9j
|
||||
cHMwHwYIKwYBBQUHAgIwEwwRRG8gV2hhdCBUaG91IFdpbHQwDQYJKoZIhvcNAQEL
|
||||
BQADggEBAJTSscrGO1ymwZ+rMF+mfVeHfplfyMzZ/6SZyvaYgO9DLr42KIETdHBg
|
||||
Y9AZ6aOKboN/hY98kb9mQ0BpOCsSaCkgTsqCjw3szsRd/FMgUSVn36vFpbX2f5oD
|
||||
gF40N/51EN5Efbe7aN4Oxmcgijh4IY2sczcskJixAd9T/hjVtv160LJ0xcHRrfji
|
||||
u/Tc2E0q+E5k4V91D2HajwU6qcGbap02JI+pX/Oq4S36yfggIUyowmXQw4nm1cb0
|
||||
cFXwrMzg+XtDHj+Ex+yBlauq+MP1rjXiHrNIO2hIiyRU9jdxfITAE4DmqEzEBZKY
|
||||
NORfB6suv4wLnAlsLbPJEdsraq4/IiU=
|
||||
-----END CERTIFICATE-----
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue