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/base64"
"encoding/binary" "encoding/binary"
"encoding/json" "encoding/json"
"encoding/pem"
"errors" "errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
@ -22,8 +23,8 @@ import (
"github.com/letsencrypt/boulder/core" "github.com/letsencrypt/boulder/core"
"github.com/letsencrypt/boulder/identifier" "github.com/letsencrypt/boulder/identifier"
"github.com/letsencrypt/boulder/probs" "github.com/letsencrypt/boulder/probs"
"github.com/letsencrypt/boulder/revocation"
"github.com/letsencrypt/boulder/test/load-generator/acme" "github.com/letsencrypt/boulder/test/load-generator/acme"
"gopkg.in/square/go-jose.v2" "gopkg.in/square/go-jose.v2"
) )
@ -31,11 +32,12 @@ var (
// stringToOperation maps a configured plan action to a function that can // stringToOperation maps a configured plan action to a function that can
// operate on a state/context. // operate on a state/context.
stringToOperation = map[string]func(*State, *context) error{ stringToOperation = map[string]func(*State, *context) error{
"newAccount": newAccount, "newAccount": newAccount,
"getAccount": getAccount, "getAccount": getAccount,
"newOrder": newOrder, "newOrder": newOrder,
"fulfillOrder": fulfillOrder, "fulfillOrder": fulfillOrder,
"finalizeOrder": finalizeOrder, "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) 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", "newAccount",
"newOrder", "newOrder",
"fulfillOrder", "fulfillOrder",
"finalizeOrder" "finalizeOrder",
"revokeCertificate"
], ],
"rate": 1, "rate": 1,
"runtime": "10s", "runtime": "10s",
@ -22,5 +23,6 @@
"regEmail": "loadtesting@letsencrypt.org", "regEmail": "loadtesting@letsencrypt.org",
"maxRegs": 20, "maxRegs": 20,
"maxNamesPerCert": 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 MaxRegs int // maximum number of registrations to create
MaxNamesPerCert int // maximum number of names on one certificate/order MaxNamesPerCert int // maximum number of names on one certificate/order
ChallengeStrategy string // challenge selection strategy ("random", "http-01", "dns-01", "tls-alpn-01") 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() { func main() {
@ -87,6 +88,7 @@ func main() {
config.RegEmail, config.RegEmail,
config.Plan.Actions, config.Plan.Actions,
config.ChallengeStrategy, config.ChallengeStrategy,
config.RevokeChance,
) )
cmd.FailOnError(err, "Failed to create load generator") cmd.FailOnError(err, "Failed to create load generator")

View File

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