load-generator: Add revocation-by-account support (#4237)

Adds RFC 8555 revocation authorized using the issuing account.
This commit is contained in:
Roland Bracewell Shoemaker 2019-05-30 08:18:19 -07:00 committed by Daniel McCarney
parent c0246b3d97
commit dc11681faa
4 changed files with 100 additions and 9 deletions

View File

@ -12,6 +12,7 @@ import (
"encoding/base64"
"encoding/binary"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"io/ioutil"
@ -22,8 +23,8 @@ import (
"github.com/letsencrypt/boulder/core"
"github.com/letsencrypt/boulder/identifier"
"github.com/letsencrypt/boulder/probs"
"github.com/letsencrypt/boulder/revocation"
"github.com/letsencrypt/boulder/test/load-generator/acme"
"gopkg.in/square/go-jose.v2"
)
@ -31,11 +32,12 @@ var (
// stringToOperation maps a configured plan action to a function that can
// operate on a state/context.
stringToOperation = map[string]func(*State, *context) error{
"newAccount": newAccount,
"getAccount": getAccount,
"newOrder": newOrder,
"fulfillOrder": fulfillOrder,
"finalizeOrder": finalizeOrder,
"newAccount": newAccount,
"getAccount": getAccount,
"newOrder": newOrder,
"fulfillOrder": fulfillOrder,
"finalizeOrder": finalizeOrder,
"revokeCertificate": revokeCertificate,
}
)
@ -582,3 +584,81 @@ func postAsGet(s *State, ctx *context, url string, latencyTag string) (*http.Res
return s.post(url, requestPayload, ctx.ns, latencyTag, http.StatusOK)
}
func popCertificate(ctx *context) string {
certIndex := mrand.Intn(len(ctx.certs))
certURL := ctx.certs[certIndex]
ctx.certs = append(ctx.certs[:certIndex], ctx.certs[certIndex+1:]...)
return certURL
}
func getCert(s *State, ctx *context, url string) ([]byte, error) {
latencyTag := "/acme/cert/{serial}"
resp, err := postAsGet(s, ctx, url, latencyTag)
if err != nil {
return nil, fmt.Errorf("%s bad response: %s", url, err)
}
defer resp.Body.Close()
return ioutil.ReadAll(resp.Body)
}
// revokeCertificate removes a certificate url from the context, retrieves it,
// and sends a revocation request for the certificate to the ACME server.
// The revocation request is signed with the account key rather than the certificate
// key.
func revokeCertificate(s *State, ctx *context) error {
if len(ctx.certs) < 1 {
return errors.New("No certificates in the context that can be revoked")
}
if r := mrand.Float32(); r > s.revokeChance {
return nil
}
certURL := popCertificate(ctx)
certPEM, err := getCert(s, ctx, certURL)
if err != nil {
return err
}
pemBlock, _ := pem.Decode(certPEM)
revokeObj := struct {
Certificate string
Reason int
}{
Certificate: base64.URLEncoding.EncodeToString(pemBlock.Bytes),
Reason: revocation.Unspecified,
}
revokeJSON, err := json.Marshal(revokeObj)
if err != nil {
return err
}
revokeURL := s.directory.EndpointURL(acme.RevokeCertEndpoint)
// TODO(roland): randomly use the certificate key to sign the request instead of
// the account key
jws, err := ctx.signKeyIDV2Request(revokeJSON, revokeURL)
if err != nil {
return err
}
requestPayload := []byte(jws.FullSerialize())
resp, err := s.post(
revokeURL,
requestPayload,
ctx.ns,
"/acme/revoke-cert",
http.StatusOK,
)
if err != nil {
return err
}
defer resp.Body.Close()
_, err = ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
return nil
}

View File

@ -4,7 +4,8 @@
"newAccount",
"newOrder",
"fulfillOrder",
"finalizeOrder"
"finalizeOrder",
"revokeCertificate"
],
"rate": 1,
"runtime": "10s",
@ -22,5 +23,6 @@
"regEmail": "loadtesting@letsencrypt.org",
"maxRegs": 20,
"maxNamesPerCert": 20,
"dontSaveState": true
"dontSaveState": true,
"revokeChance": 0.5
}

View File

@ -36,6 +36,7 @@ type Config struct {
MaxRegs int // maximum number of registrations to create
MaxNamesPerCert int // maximum number of names on one certificate/order
ChallengeStrategy string // challenge selection strategy ("random", "http-01", "dns-01", "tls-alpn-01")
RevokeChance float32 // chance of revoking certificate after issuance, between 0.0 and 1.0
}
func main() {
@ -87,6 +88,7 @@ func main() {
config.RegEmail,
config.Plan.Actions,
config.ChallengeStrategy,
config.RevokeChance,
)
cmd.FailOnError(err, "Failed to create load generator")

View File

@ -188,6 +188,8 @@ type State struct {
challStrat acme.ChallengeStrategy
httpClient *http.Client
revokeChance float32
reqTotal int64
respCodes map[int]*respCode
cMu sync.Mutex
@ -288,7 +290,8 @@ func New(
latencyPath string,
userEmail string,
operations []string,
challStrat string) (*State, error) {
challStrat string,
revokeChance float32) (*State, error) {
certKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, err
@ -301,6 +304,9 @@ func New(
if err != nil {
return nil, err
}
if revokeChance > 1 {
return nil, errors.New("revokeChance must be between 0.0 and 1.0")
}
httpClient := &http.Client{
Transport: &http.Transport{
DialContext: (&net.Dialer{
@ -333,6 +339,7 @@ func New(
maxNamesPerCert: maxNamesPerCert,
email: userEmail,
respCodes: make(map[int]*respCode),
revokeChance: revokeChance,
}
// convert operations strings to methods