204 lines
5.2 KiB
Go
204 lines
5.2 KiB
Go
//go:build integration
|
|
|
|
package integration
|
|
|
|
import (
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"crypto/rand"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/asn1"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
|
|
challTestSrvClient "github.com/letsencrypt/boulder/test/chall-test-srv-client"
|
|
|
|
"github.com/eggsampler/acme/v3"
|
|
)
|
|
|
|
var testSrvClient = challTestSrvClient.NewClient("")
|
|
|
|
func init() {
|
|
// Go tests get run in the directory their source code lives in. For these
|
|
// test cases, that would be "test/integration." However, it's easier to
|
|
// reference test data and config files for integration tests relative to the
|
|
// root of the Boulder repo, so we run all of these tests from there instead.
|
|
os.Chdir("../../")
|
|
}
|
|
|
|
var (
|
|
OIDExtensionCTPoison = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 4, 3}
|
|
)
|
|
|
|
func random_domain() string {
|
|
var bytes [3]byte
|
|
rand.Read(bytes[:])
|
|
return hex.EncodeToString(bytes[:]) + ".com"
|
|
}
|
|
|
|
type client struct {
|
|
acme.Account
|
|
acme.Client
|
|
}
|
|
|
|
func makeClient(contacts ...string) (*client, error) {
|
|
c, err := acme.NewClient("http://boulder.service.consul:4001/directory")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Error connecting to acme directory: %v", err)
|
|
}
|
|
privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error creating private key: %v", err)
|
|
}
|
|
account, err := c.NewAccount(privKey, false, true, contacts...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &client{account, c}, nil
|
|
}
|
|
|
|
func makeClientAndOrder(c *client, csrKey *ecdsa.PrivateKey, idents []acme.Identifier, cn bool, profile string, certToReplace *x509.Certificate) (*client, *acme.Order, error) {
|
|
var err error
|
|
if c == nil {
|
|
c, err = makeClient()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
}
|
|
|
|
var order acme.Order
|
|
if certToReplace != nil {
|
|
order, err = c.Client.ReplacementOrderExtension(c.Account, certToReplace, idents, acme.OrderExtension{Profile: profile})
|
|
} else {
|
|
order, err = c.Client.NewOrderExtension(c.Account, idents, acme.OrderExtension{Profile: profile})
|
|
}
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
for _, authUrl := range order.Authorizations {
|
|
auth, err := c.Client.FetchAuthorization(c.Account, authUrl)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("fetching authorization at %s: %s", authUrl, err)
|
|
}
|
|
|
|
chal, ok := auth.ChallengeMap[acme.ChallengeTypeHTTP01]
|
|
if !ok {
|
|
return nil, nil, fmt.Errorf("no HTTP challenge at %s", authUrl)
|
|
}
|
|
|
|
_, err = testSrvClient.AddHTTP01Response(chal.Token, chal.KeyAuthorization)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
chal, err = c.Client.UpdateChallenge(c.Account, chal)
|
|
if err != nil {
|
|
testSrvClient.RemoveHTTP01Response(chal.Token)
|
|
return nil, nil, err
|
|
}
|
|
_, err = testSrvClient.RemoveHTTP01Response(chal.Token)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
}
|
|
|
|
csr, err := makeCSR(csrKey, idents, cn)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
order, err = c.Client.FinalizeOrder(c.Account, order, csr)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("finalizing order: %s", err)
|
|
}
|
|
|
|
return c, &order, nil
|
|
}
|
|
|
|
type issuanceResult struct {
|
|
acme.Order
|
|
certs []*x509.Certificate
|
|
}
|
|
|
|
func authAndIssue(c *client, csrKey *ecdsa.PrivateKey, idents []acme.Identifier, cn bool, profile string) (*issuanceResult, error) {
|
|
var err error
|
|
|
|
c, order, err := makeClientAndOrder(c, csrKey, idents, cn, profile, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
certs, err := c.Client.FetchCertificates(c.Account, order.Certificate)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("fetching certificates: %s", err)
|
|
}
|
|
return &issuanceResult{*order, certs}, nil
|
|
}
|
|
|
|
type issuanceResultAllChains struct {
|
|
acme.Order
|
|
certs map[string][]*x509.Certificate
|
|
}
|
|
|
|
func authAndIssueFetchAllChains(c *client, csrKey *ecdsa.PrivateKey, idents []acme.Identifier, cn bool) (*issuanceResultAllChains, error) {
|
|
c, order, err := makeClientAndOrder(c, csrKey, idents, cn, "", nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Retrieve all the certificate chains served by the WFE2.
|
|
certs, err := c.Client.FetchAllCertificates(c.Account, order.Certificate)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("fetching certificates: %s", err)
|
|
}
|
|
|
|
return &issuanceResultAllChains{*order, certs}, nil
|
|
}
|
|
|
|
func makeCSR(k *ecdsa.PrivateKey, idents []acme.Identifier, cn bool) (*x509.CertificateRequest, error) {
|
|
var err error
|
|
if k == nil {
|
|
k, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("generating certificate key: %s", err)
|
|
}
|
|
}
|
|
|
|
var names []string
|
|
var ips []net.IP
|
|
for _, ident := range idents {
|
|
switch ident.Type {
|
|
case "dns":
|
|
names = append(names, ident.Value)
|
|
case "ip":
|
|
ips = append(ips, net.ParseIP(ident.Value))
|
|
default:
|
|
return nil, fmt.Errorf("unrecognized identifier type %q", ident.Type)
|
|
}
|
|
}
|
|
|
|
tmpl := &x509.CertificateRequest{
|
|
SignatureAlgorithm: x509.ECDSAWithSHA256,
|
|
PublicKeyAlgorithm: x509.ECDSA,
|
|
PublicKey: k.Public(),
|
|
DNSNames: names,
|
|
IPAddresses: ips,
|
|
}
|
|
if cn && len(names) > 0 {
|
|
tmpl.Subject = pkix.Name{CommonName: names[0]}
|
|
}
|
|
|
|
csrDer, err := x509.CreateCertificateRequest(rand.Reader, tmpl, k)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("making csr: %s", err)
|
|
}
|
|
csr, err := x509.ParseCertificateRequest(csrDer)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parsing csr: %s", err)
|
|
}
|
|
return csr, nil
|
|
}
|