4861 lines
164 KiB
Go
4861 lines
164 KiB
Go
package sa
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/sha256"
|
|
"crypto/x509"
|
|
"database/sql"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"math/big"
|
|
"math/bits"
|
|
mrand "math/rand/v2"
|
|
"net"
|
|
"os"
|
|
"reflect"
|
|
"slices"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/go-jose/go-jose/v4"
|
|
"github.com/go-sql-driver/mysql"
|
|
"github.com/jmhodges/clock"
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"golang.org/x/crypto/ocsp"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/protobuf/types/known/durationpb"
|
|
"google.golang.org/protobuf/types/known/emptypb"
|
|
"google.golang.org/protobuf/types/known/timestamppb"
|
|
|
|
"github.com/letsencrypt/boulder/core"
|
|
corepb "github.com/letsencrypt/boulder/core/proto"
|
|
"github.com/letsencrypt/boulder/db"
|
|
berrors "github.com/letsencrypt/boulder/errors"
|
|
"github.com/letsencrypt/boulder/features"
|
|
bgrpc "github.com/letsencrypt/boulder/grpc"
|
|
"github.com/letsencrypt/boulder/identifier"
|
|
blog "github.com/letsencrypt/boulder/log"
|
|
"github.com/letsencrypt/boulder/metrics"
|
|
"github.com/letsencrypt/boulder/probs"
|
|
"github.com/letsencrypt/boulder/revocation"
|
|
sapb "github.com/letsencrypt/boulder/sa/proto"
|
|
"github.com/letsencrypt/boulder/test"
|
|
"github.com/letsencrypt/boulder/test/vars"
|
|
)
|
|
|
|
var log = blog.UseMock()
|
|
var ctx = context.Background()
|
|
|
|
var (
|
|
theKey = `{
|
|
"kty": "RSA",
|
|
"n": "n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT-O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqVwGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuCLqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5gHdrNP5zw",
|
|
"e": "AQAB"
|
|
}`
|
|
)
|
|
|
|
type fakeServerStream[T any] struct {
|
|
grpc.ServerStream
|
|
output chan<- *T
|
|
}
|
|
|
|
func (s *fakeServerStream[T]) Send(msg *T) error {
|
|
s.output <- msg
|
|
return nil
|
|
}
|
|
|
|
func (s *fakeServerStream[T]) Context() context.Context {
|
|
return context.Background()
|
|
}
|
|
|
|
// initSA constructs a SQLStorageAuthority and a clean up function that should
|
|
// be defer'ed to the end of the test.
|
|
func initSA(t *testing.T) (*SQLStorageAuthority, clock.FakeClock, func()) {
|
|
t.Helper()
|
|
features.Reset()
|
|
|
|
dbMap, err := DBMapForTest(vars.DBConnSA)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create dbMap: %s", err)
|
|
}
|
|
|
|
dbIncidentsMap, err := DBMapForTest(vars.DBConnIncidents)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create dbMap: %s", err)
|
|
}
|
|
|
|
fc := clock.NewFake()
|
|
fc.Set(time.Date(2015, 3, 4, 5, 0, 0, 0, time.UTC))
|
|
|
|
saro, err := NewSQLStorageAuthorityRO(dbMap, dbIncidentsMap, metrics.NoopRegisterer, 1, 0, fc, log)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create SA: %s", err)
|
|
}
|
|
|
|
sa, err := NewSQLStorageAuthorityWrapping(saro, dbMap, metrics.NoopRegisterer)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create SA: %s", err)
|
|
}
|
|
|
|
return sa, fc, test.ResetBoulderTestDatabase(t)
|
|
}
|
|
|
|
// CreateWorkingTestRegistration inserts a new, correct Registration into the
|
|
// given SA.
|
|
func createWorkingRegistration(t *testing.T, sa *SQLStorageAuthority) *corepb.Registration {
|
|
initialIP, _ := net.ParseIP("88.77.66.11").MarshalText()
|
|
reg, err := sa.NewRegistration(context.Background(), &corepb.Registration{
|
|
Key: []byte(theKey),
|
|
Contact: []string{"mailto:foo@example.com"},
|
|
InitialIP: initialIP,
|
|
CreatedAt: timestamppb.New(time.Date(2003, 5, 10, 0, 0, 0, 0, time.UTC)),
|
|
Status: string(core.StatusValid),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Unable to create new registration: %s", err)
|
|
}
|
|
return reg
|
|
}
|
|
|
|
func createPendingAuthorization(t *testing.T, sa *SQLStorageAuthority, domain string, exp time.Time) int64 {
|
|
t.Helper()
|
|
|
|
tokenStr := core.NewToken()
|
|
token, err := base64.RawURLEncoding.DecodeString(tokenStr)
|
|
test.AssertNotError(t, err, "computing test authorization challenge token")
|
|
|
|
am := authzModel{
|
|
IdentifierType: 0, // dnsName
|
|
IdentifierValue: domain,
|
|
RegistrationID: 1,
|
|
Status: statusToUint[core.StatusPending],
|
|
Expires: exp,
|
|
Challenges: 1 << challTypeToUint[string(core.ChallengeTypeHTTP01)],
|
|
Token: token,
|
|
}
|
|
|
|
err = sa.dbMap.Insert(context.Background(), &am)
|
|
test.AssertNotError(t, err, "creating test authorization")
|
|
|
|
return am.ID
|
|
}
|
|
|
|
func createFinalizedAuthorization(t *testing.T, sa *SQLStorageAuthority, domain string, exp time.Time,
|
|
status string, attemptedAt time.Time) int64 {
|
|
t.Helper()
|
|
pendingID := createPendingAuthorization(t, sa, domain, exp)
|
|
attempted := string(core.ChallengeTypeHTTP01)
|
|
_, err := sa.FinalizeAuthorization2(context.Background(), &sapb.FinalizeAuthorizationRequest{
|
|
Id: pendingID,
|
|
Status: status,
|
|
Expires: timestamppb.New(exp),
|
|
Attempted: attempted,
|
|
AttemptedAt: timestamppb.New(attemptedAt),
|
|
})
|
|
test.AssertNotError(t, err, "sa.FinalizeAuthorizations2 failed")
|
|
return pendingID
|
|
}
|
|
|
|
func goodTestJWK() *jose.JSONWebKey {
|
|
var jwk jose.JSONWebKey
|
|
err := json.Unmarshal([]byte(theKey), &jwk)
|
|
if err != nil {
|
|
panic("known-good theKey is no longer known-good")
|
|
}
|
|
return &jwk
|
|
}
|
|
|
|
func TestAddRegistration(t *testing.T) {
|
|
sa, clk, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
jwk := goodTestJWK()
|
|
jwkJSON, _ := jwk.MarshalJSON()
|
|
|
|
contacts := []string{"mailto:foo@example.com"}
|
|
initialIP, _ := net.ParseIP("43.34.43.34").MarshalText()
|
|
reg, err := sa.NewRegistration(ctx, &corepb.Registration{
|
|
Key: jwkJSON,
|
|
Contact: contacts,
|
|
InitialIP: initialIP,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Couldn't create new registration: %s", err)
|
|
}
|
|
test.Assert(t, reg.Id != 0, "ID shouldn't be 0")
|
|
test.AssertDeepEquals(t, reg.Contact, contacts)
|
|
|
|
_, err = sa.GetRegistration(ctx, &sapb.RegistrationID{Id: 0})
|
|
test.AssertError(t, err, "Registration object for ID 0 was returned")
|
|
|
|
dbReg, err := sa.GetRegistration(ctx, &sapb.RegistrationID{Id: reg.Id})
|
|
test.AssertNotError(t, err, fmt.Sprintf("Couldn't get registration with ID %v", reg.Id))
|
|
|
|
createdAt := clk.Now()
|
|
test.AssertEquals(t, dbReg.Id, reg.Id)
|
|
test.AssertByteEquals(t, dbReg.Key, jwkJSON)
|
|
test.AssertDeepEquals(t, dbReg.CreatedAt.AsTime(), createdAt)
|
|
|
|
initialIP, _ = net.ParseIP("72.72.72.72").MarshalText()
|
|
newReg := &corepb.Registration{
|
|
Id: reg.Id,
|
|
Key: jwkJSON,
|
|
Contact: []string{"test.com"},
|
|
InitialIP: initialIP,
|
|
Agreement: "yes",
|
|
}
|
|
_, err = sa.UpdateRegistration(ctx, newReg)
|
|
test.AssertNotError(t, err, fmt.Sprintf("Couldn't get registration with ID %v", reg.Id))
|
|
dbReg, err = sa.GetRegistrationByKey(ctx, &sapb.JSONWebKey{Jwk: jwkJSON})
|
|
test.AssertNotError(t, err, "Couldn't get registration by key")
|
|
|
|
test.AssertEquals(t, dbReg.Id, newReg.Id)
|
|
test.AssertEquals(t, dbReg.Agreement, newReg.Agreement)
|
|
|
|
anotherKey := `{
|
|
"kty":"RSA",
|
|
"n": "vd7rZIoTLEe-z1_8G1FcXSw9CQFEJgV4g9V277sER7yx5Qjz_Pkf2YVth6wwwFJEmzc0hoKY-MMYFNwBE4hQHw",
|
|
"e":"AQAB"
|
|
}`
|
|
|
|
_, err = sa.GetRegistrationByKey(ctx, &sapb.JSONWebKey{Jwk: []byte(anotherKey)})
|
|
test.AssertError(t, err, "Registration object for invalid key was returned")
|
|
}
|
|
|
|
func TestNoSuchRegistrationErrors(t *testing.T) {
|
|
sa, _, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
_, err := sa.GetRegistration(ctx, &sapb.RegistrationID{Id: 100})
|
|
test.AssertErrorIs(t, err, berrors.NotFound)
|
|
|
|
jwk := goodTestJWK()
|
|
jwkJSON, _ := jwk.MarshalJSON()
|
|
|
|
_, err = sa.GetRegistrationByKey(ctx, &sapb.JSONWebKey{Jwk: jwkJSON})
|
|
test.AssertErrorIs(t, err, berrors.NotFound)
|
|
|
|
_, err = sa.UpdateRegistration(ctx, &corepb.Registration{Id: 100, Key: jwkJSON, InitialIP: []byte("foo")})
|
|
test.AssertErrorIs(t, err, berrors.NotFound)
|
|
}
|
|
|
|
func TestSelectRegistration(t *testing.T) {
|
|
sa, _, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
var ctx = context.Background()
|
|
jwk := goodTestJWK()
|
|
jwkJSON, _ := jwk.MarshalJSON()
|
|
sha, err := core.KeyDigestB64(jwk.Key)
|
|
test.AssertNotError(t, err, "couldn't parse jwk.Key")
|
|
|
|
initialIP, _ := net.ParseIP("43.34.43.34").MarshalText()
|
|
reg, err := sa.NewRegistration(ctx, &corepb.Registration{
|
|
Key: jwkJSON,
|
|
Contact: []string{"mailto:foo@example.com"},
|
|
InitialIP: initialIP,
|
|
})
|
|
test.AssertNotError(t, err, fmt.Sprintf("couldn't create new registration: %s", err))
|
|
test.Assert(t, reg.Id != 0, "ID shouldn't be 0")
|
|
|
|
_, err = selectRegistration(ctx, sa.dbMap, "id", reg.Id)
|
|
test.AssertNotError(t, err, "selecting by id should work")
|
|
_, err = selectRegistration(ctx, sa.dbMap, "jwk_sha256", sha)
|
|
test.AssertNotError(t, err, "selecting by jwk_sha256 should work")
|
|
_, err = selectRegistration(ctx, sa.dbMap, "initialIP", reg.Id)
|
|
test.AssertError(t, err, "selecting by any other column should not work")
|
|
}
|
|
|
|
func TestReplicationLagRetries(t *testing.T) {
|
|
sa, clk, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
reg := createWorkingRegistration(t, sa)
|
|
|
|
// First, set the lagFactor to 0. Neither selecting a real registration nor
|
|
// selecting a nonexistent registration should cause the clock to advance.
|
|
sa.lagFactor = 0
|
|
start := clk.Now()
|
|
|
|
_, err := sa.GetRegistration(ctx, &sapb.RegistrationID{Id: reg.Id})
|
|
test.AssertNotError(t, err, "selecting extant registration")
|
|
test.AssertEquals(t, clk.Now(), start)
|
|
test.AssertMetricWithLabelsEquals(t, sa.lagFactorCounter, prometheus.Labels{"method": "GetRegistration", "result": "notfound"}, 0)
|
|
|
|
_, err = sa.GetRegistration(ctx, &sapb.RegistrationID{Id: reg.Id + 1})
|
|
test.AssertError(t, err, "selecting nonexistent registration")
|
|
test.AssertEquals(t, clk.Now(), start)
|
|
// With lagFactor disabled, we should never enter the retry codepath, as a
|
|
// result the metric should not increment.
|
|
test.AssertMetricWithLabelsEquals(t, sa.lagFactorCounter, prometheus.Labels{"method": "GetRegistration", "result": "notfound"}, 0)
|
|
|
|
// Now, set the lagFactor to 1. Trying to select a nonexistent registration
|
|
// should cause the clock to advance when GetRegistration sleeps and retries.
|
|
sa.lagFactor = 1
|
|
start = clk.Now()
|
|
|
|
_, err = sa.GetRegistration(ctx, &sapb.RegistrationID{Id: reg.Id})
|
|
test.AssertNotError(t, err, "selecting extant registration")
|
|
test.AssertEquals(t, clk.Now(), start)
|
|
// lagFactor is enabled, but the registration exists.
|
|
test.AssertMetricWithLabelsEquals(t, sa.lagFactorCounter, prometheus.Labels{"method": "GetRegistration", "result": "notfound"}, 0)
|
|
|
|
_, err = sa.GetRegistration(ctx, &sapb.RegistrationID{Id: reg.Id + 1})
|
|
test.AssertError(t, err, "selecting nonexistent registration")
|
|
test.AssertEquals(t, clk.Now(), start.Add(1))
|
|
// With lagFactor enabled, we should enter the retry codepath and as a result
|
|
// the metric should increment.
|
|
test.AssertMetricWithLabelsEquals(t, sa.lagFactorCounter, prometheus.Labels{"method": "GetRegistration", "result": "notfound"}, 1)
|
|
}
|
|
|
|
// findIssuedName is a small helper test function to directly query the
|
|
// issuedNames table for a given name to find a serial (or return an err).
|
|
func findIssuedName(ctx context.Context, dbMap db.OneSelector, name string) (string, error) {
|
|
var issuedNamesSerial string
|
|
err := dbMap.SelectOne(
|
|
ctx,
|
|
&issuedNamesSerial,
|
|
`SELECT serial FROM issuedNames
|
|
WHERE reversedName = ?
|
|
ORDER BY notBefore DESC
|
|
LIMIT 1`,
|
|
ReverseName(name))
|
|
return issuedNamesSerial, err
|
|
}
|
|
|
|
func TestAddSerial(t *testing.T) {
|
|
sa, clk, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
reg := createWorkingRegistration(t, sa)
|
|
serial, testCert := test.ThrowAwayCert(t, clk)
|
|
|
|
_, err := sa.AddSerial(context.Background(), &sapb.AddSerialRequest{
|
|
RegID: reg.Id,
|
|
Created: timestamppb.New(testCert.NotBefore),
|
|
Expires: timestamppb.New(testCert.NotAfter),
|
|
})
|
|
test.AssertError(t, err, "adding without serial should fail")
|
|
|
|
_, err = sa.AddSerial(context.Background(), &sapb.AddSerialRequest{
|
|
Serial: serial,
|
|
Created: timestamppb.New(testCert.NotBefore),
|
|
Expires: timestamppb.New(testCert.NotAfter),
|
|
})
|
|
test.AssertError(t, err, "adding without regid should fail")
|
|
|
|
_, err = sa.AddSerial(context.Background(), &sapb.AddSerialRequest{
|
|
Serial: serial,
|
|
RegID: reg.Id,
|
|
Expires: timestamppb.New(testCert.NotAfter),
|
|
})
|
|
test.AssertError(t, err, "adding without created should fail")
|
|
|
|
_, err = sa.AddSerial(context.Background(), &sapb.AddSerialRequest{
|
|
Serial: serial,
|
|
RegID: reg.Id,
|
|
Created: timestamppb.New(testCert.NotBefore),
|
|
})
|
|
test.AssertError(t, err, "adding without expires should fail")
|
|
|
|
_, err = sa.AddSerial(context.Background(), &sapb.AddSerialRequest{
|
|
Serial: serial,
|
|
RegID: reg.Id,
|
|
Created: timestamppb.New(testCert.NotBefore),
|
|
Expires: timestamppb.New(testCert.NotAfter),
|
|
})
|
|
test.AssertNotError(t, err, "adding serial should have succeeded")
|
|
}
|
|
|
|
func TestGetSerialMetadata(t *testing.T) {
|
|
sa, clk, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
reg := createWorkingRegistration(t, sa)
|
|
serial, _ := test.ThrowAwayCert(t, clk)
|
|
|
|
_, err := sa.GetSerialMetadata(context.Background(), &sapb.Serial{Serial: serial})
|
|
test.AssertError(t, err, "getting nonexistent serial should have failed")
|
|
|
|
now := clk.Now()
|
|
hourLater := now.Add(time.Hour)
|
|
_, err = sa.AddSerial(context.Background(), &sapb.AddSerialRequest{
|
|
Serial: serial,
|
|
RegID: reg.Id,
|
|
Created: timestamppb.New(now),
|
|
Expires: timestamppb.New(hourLater),
|
|
})
|
|
test.AssertNotError(t, err, "failed to add test serial")
|
|
|
|
m, err := sa.GetSerialMetadata(context.Background(), &sapb.Serial{Serial: serial})
|
|
|
|
test.AssertNotError(t, err, "getting serial should have succeeded")
|
|
test.AssertEquals(t, m.Serial, serial)
|
|
test.AssertEquals(t, m.RegistrationID, reg.Id)
|
|
test.AssertEquals(t, now, timestamppb.New(now).AsTime())
|
|
test.AssertEquals(t, m.Expires.AsTime(), timestamppb.New(hourLater).AsTime())
|
|
}
|
|
|
|
func TestAddPrecertificate(t *testing.T) {
|
|
ctx := context.Background()
|
|
sa, clk, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
reg := createWorkingRegistration(t, sa)
|
|
|
|
// Create a throw-away self signed certificate with a random name and
|
|
// serial number
|
|
serial, testCert := test.ThrowAwayCert(t, clk)
|
|
|
|
// Add the cert as a precertificate
|
|
regID := reg.Id
|
|
issuedTime := time.Date(2018, 4, 1, 7, 0, 0, 0, time.UTC)
|
|
_, err := sa.AddPrecertificate(ctx, &sapb.AddCertificateRequest{
|
|
Der: testCert.Raw,
|
|
RegID: regID,
|
|
Issued: timestamppb.New(issuedTime),
|
|
IssuerNameID: 1,
|
|
})
|
|
test.AssertNotError(t, err, "Couldn't add test cert")
|
|
|
|
// It should have the expected certificate status
|
|
certStatus, err := sa.GetCertificateStatus(ctx, &sapb.Serial{Serial: serial})
|
|
test.AssertNotError(t, err, "Couldn't get status for test cert")
|
|
test.AssertEquals(t, certStatus.Status, string(core.OCSPStatusGood))
|
|
now := clk.Now()
|
|
test.AssertEquals(t, now, certStatus.OcspLastUpdated.AsTime())
|
|
|
|
// It should show up in the issued names table
|
|
issuedNamesSerial, err := findIssuedName(ctx, sa.dbMap, testCert.DNSNames[0])
|
|
test.AssertNotError(t, err, "expected no err querying issuedNames for precert")
|
|
test.AssertEquals(t, issuedNamesSerial, serial)
|
|
|
|
// We should also be able to call AddCertificate with the same cert
|
|
// without it being an error. The duplicate err on inserting to
|
|
// issuedNames should be ignored.
|
|
_, err = sa.AddCertificate(ctx, &sapb.AddCertificateRequest{
|
|
Der: testCert.Raw,
|
|
RegID: regID,
|
|
Issued: timestamppb.New(issuedTime),
|
|
})
|
|
test.AssertNotError(t, err, "unexpected err adding final cert after precert")
|
|
}
|
|
|
|
func TestAddPrecertificateNoOCSP(t *testing.T) {
|
|
sa, clk, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
reg := createWorkingRegistration(t, sa)
|
|
_, testCert := test.ThrowAwayCert(t, clk)
|
|
|
|
regID := reg.Id
|
|
issuedTime := time.Date(2018, 4, 1, 7, 0, 0, 0, time.UTC)
|
|
_, err := sa.AddPrecertificate(ctx, &sapb.AddCertificateRequest{
|
|
Der: testCert.Raw,
|
|
RegID: regID,
|
|
Issued: timestamppb.New(issuedTime),
|
|
IssuerNameID: 1,
|
|
})
|
|
test.AssertNotError(t, err, "Couldn't add test cert")
|
|
}
|
|
|
|
func TestAddPreCertificateDuplicate(t *testing.T) {
|
|
sa, clk, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
reg := createWorkingRegistration(t, sa)
|
|
|
|
_, testCert := test.ThrowAwayCert(t, clk)
|
|
issuedTime := clk.Now()
|
|
|
|
_, err := sa.AddPrecertificate(ctx, &sapb.AddCertificateRequest{
|
|
Der: testCert.Raw,
|
|
Issued: timestamppb.New(issuedTime),
|
|
RegID: reg.Id,
|
|
IssuerNameID: 1,
|
|
})
|
|
test.AssertNotError(t, err, "Couldn't add test certificate")
|
|
|
|
_, err = sa.AddPrecertificate(ctx, &sapb.AddCertificateRequest{
|
|
Der: testCert.Raw,
|
|
Issued: timestamppb.New(issuedTime),
|
|
RegID: reg.Id,
|
|
IssuerNameID: 1,
|
|
})
|
|
test.AssertDeepEquals(t, err, berrors.DuplicateError("cannot add a duplicate cert"))
|
|
}
|
|
|
|
func TestAddPrecertificateIncomplete(t *testing.T) {
|
|
sa, clk, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
reg := createWorkingRegistration(t, sa)
|
|
|
|
// Create a throw-away self signed certificate with a random name and
|
|
// serial number
|
|
_, testCert := test.ThrowAwayCert(t, clk)
|
|
|
|
// Add the cert as a precertificate
|
|
regID := reg.Id
|
|
issuedTime := time.Date(2018, 4, 1, 7, 0, 0, 0, time.UTC)
|
|
_, err := sa.AddPrecertificate(ctx, &sapb.AddCertificateRequest{
|
|
Der: testCert.Raw,
|
|
RegID: regID,
|
|
Issued: timestamppb.New(issuedTime),
|
|
// Leaving out IssuerNameID
|
|
})
|
|
|
|
test.AssertError(t, err, "Adding precert with no issuer did not fail")
|
|
}
|
|
|
|
func TestAddPrecertificateKeyHash(t *testing.T) {
|
|
sa, clk, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
reg := createWorkingRegistration(t, sa)
|
|
|
|
serial, testCert := test.ThrowAwayCert(t, clk)
|
|
_, err := sa.AddPrecertificate(ctx, &sapb.AddCertificateRequest{
|
|
Der: testCert.Raw,
|
|
RegID: reg.Id,
|
|
Issued: timestamppb.New(testCert.NotBefore),
|
|
IssuerNameID: 1,
|
|
})
|
|
test.AssertNotError(t, err, "failed to add precert")
|
|
|
|
var keyHashes []keyHashModel
|
|
_, err = sa.dbMap.Select(context.Background(), &keyHashes, "SELECT * FROM keyHashToSerial")
|
|
test.AssertNotError(t, err, "failed to retrieve rows from keyHashToSerial")
|
|
test.AssertEquals(t, len(keyHashes), 1)
|
|
test.AssertEquals(t, keyHashes[0].CertSerial, serial)
|
|
test.AssertEquals(t, keyHashes[0].CertNotAfter, testCert.NotAfter)
|
|
test.AssertEquals(t, keyHashes[0].CertNotAfter, timestamppb.New(testCert.NotAfter).AsTime())
|
|
spkiHash := sha256.Sum256(testCert.RawSubjectPublicKeyInfo)
|
|
test.Assert(t, bytes.Equal(keyHashes[0].KeyHash, spkiHash[:]), "spki hash mismatch")
|
|
}
|
|
|
|
func TestAddCertificate(t *testing.T) {
|
|
sa, clk, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
reg := createWorkingRegistration(t, sa)
|
|
|
|
serial, testCert := test.ThrowAwayCert(t, clk)
|
|
|
|
issuedTime := sa.clk.Now()
|
|
_, err := sa.AddCertificate(ctx, &sapb.AddCertificateRequest{
|
|
Der: testCert.Raw,
|
|
RegID: reg.Id,
|
|
Issued: timestamppb.New(issuedTime),
|
|
})
|
|
test.AssertNotError(t, err, "Couldn't add test cert")
|
|
|
|
retrievedCert, err := sa.GetCertificate(ctx, &sapb.Serial{Serial: serial})
|
|
test.AssertNotError(t, err, "Couldn't get test cert by full serial")
|
|
test.AssertByteEquals(t, testCert.Raw, retrievedCert.Der)
|
|
test.AssertEquals(t, retrievedCert.Issued.AsTime(), issuedTime)
|
|
|
|
// Calling AddCertificate with empty args should fail.
|
|
_, err = sa.AddCertificate(ctx, &sapb.AddCertificateRequest{
|
|
Der: nil,
|
|
RegID: reg.Id,
|
|
Issued: timestamppb.New(issuedTime),
|
|
})
|
|
test.AssertError(t, err, "shouldn't be able to add cert with no DER")
|
|
_, err = sa.AddCertificate(ctx, &sapb.AddCertificateRequest{
|
|
Der: testCert.Raw,
|
|
RegID: 0,
|
|
Issued: timestamppb.New(issuedTime),
|
|
})
|
|
test.AssertError(t, err, "shouldn't be able to add cert with no regID")
|
|
_, err = sa.AddCertificate(ctx, &sapb.AddCertificateRequest{
|
|
Der: testCert.Raw,
|
|
RegID: reg.Id,
|
|
Issued: nil,
|
|
})
|
|
test.AssertError(t, err, "shouldn't be able to add cert with no issued timestamp")
|
|
_, err = sa.AddCertificate(ctx, &sapb.AddCertificateRequest{
|
|
Der: testCert.Raw,
|
|
RegID: reg.Id,
|
|
Issued: timestamppb.New(time.Time{}),
|
|
})
|
|
test.AssertError(t, err, "shouldn't be able to add cert with zero issued timestamp")
|
|
}
|
|
|
|
func TestAddCertificateDuplicate(t *testing.T) {
|
|
sa, clk, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
reg := createWorkingRegistration(t, sa)
|
|
|
|
_, testCert := test.ThrowAwayCert(t, clk)
|
|
|
|
issuedTime := clk.Now()
|
|
_, err := sa.AddCertificate(ctx, &sapb.AddCertificateRequest{
|
|
Der: testCert.Raw,
|
|
RegID: reg.Id,
|
|
Issued: timestamppb.New(issuedTime),
|
|
})
|
|
test.AssertNotError(t, err, "Couldn't add test certificate")
|
|
|
|
_, err = sa.AddCertificate(ctx, &sapb.AddCertificateRequest{
|
|
Der: testCert.Raw,
|
|
RegID: reg.Id,
|
|
Issued: timestamppb.New(issuedTime),
|
|
})
|
|
test.AssertDeepEquals(t, err, berrors.DuplicateError("cannot add a duplicate cert"))
|
|
|
|
}
|
|
|
|
func TestCountCertificatesByNamesTimeRange(t *testing.T) {
|
|
sa, clk, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
reg := createWorkingRegistration(t, sa)
|
|
_, testCert := test.ThrowAwayCert(t, clk)
|
|
_, err := sa.AddCertificate(ctx, &sapb.AddCertificateRequest{
|
|
Der: testCert.Raw,
|
|
RegID: reg.Id,
|
|
Issued: timestamppb.New(testCert.NotBefore),
|
|
})
|
|
test.AssertNotError(t, err, "Couldn't add test cert")
|
|
name := testCert.DNSNames[0]
|
|
|
|
// Move time forward, so the cert was issued slightly in the past.
|
|
clk.Add(time.Hour)
|
|
now := clk.Now()
|
|
yesterday := clk.Now().Add(-24 * time.Hour)
|
|
twoDaysAgo := clk.Now().Add(-48 * time.Hour)
|
|
tomorrow := clk.Now().Add(24 * time.Hour)
|
|
|
|
// Count for a name that doesn't have any certs
|
|
counts, err := sa.CountCertificatesByNames(ctx, &sapb.CountCertificatesByNamesRequest{
|
|
DnsNames: []string{"does.not.exist"},
|
|
Range: &sapb.Range{
|
|
Earliest: timestamppb.New(yesterday),
|
|
Latest: timestamppb.New(now),
|
|
},
|
|
})
|
|
test.AssertNotError(t, err, "Error counting certs.")
|
|
test.AssertEquals(t, len(counts.Counts), 1)
|
|
test.AssertEquals(t, counts.Counts["does.not.exist"], int64(0))
|
|
|
|
// Time range including now should find the cert.
|
|
counts, err = sa.CountCertificatesByNames(ctx, &sapb.CountCertificatesByNamesRequest{
|
|
DnsNames: testCert.DNSNames,
|
|
Range: &sapb.Range{
|
|
Earliest: timestamppb.New(yesterday),
|
|
Latest: timestamppb.New(now),
|
|
},
|
|
})
|
|
test.AssertNotError(t, err, "sa.CountCertificatesByName failed")
|
|
test.AssertEquals(t, len(counts.Counts), 1)
|
|
test.AssertEquals(t, counts.Counts[name], int64(1))
|
|
|
|
// Time range between two days ago and yesterday should not find the cert.
|
|
counts, err = sa.CountCertificatesByNames(ctx, &sapb.CountCertificatesByNamesRequest{
|
|
DnsNames: testCert.DNSNames,
|
|
Range: &sapb.Range{
|
|
Earliest: timestamppb.New(twoDaysAgo),
|
|
Latest: timestamppb.New(yesterday),
|
|
},
|
|
})
|
|
test.AssertNotError(t, err, "Error counting certs.")
|
|
test.AssertEquals(t, len(counts.Counts), 1)
|
|
test.AssertEquals(t, counts.Counts[name], int64(0))
|
|
|
|
// Time range between now and tomorrow also should not (time ranges are
|
|
// inclusive at the tail end, but not the beginning end).
|
|
counts, err = sa.CountCertificatesByNames(ctx, &sapb.CountCertificatesByNamesRequest{
|
|
DnsNames: testCert.DNSNames,
|
|
Range: &sapb.Range{
|
|
Earliest: timestamppb.New(now),
|
|
Latest: timestamppb.New(tomorrow),
|
|
},
|
|
})
|
|
test.AssertNotError(t, err, "Error counting certs.")
|
|
test.AssertEquals(t, len(counts.Counts), 1)
|
|
test.AssertEquals(t, counts.Counts[name], int64(0))
|
|
}
|
|
|
|
func TestCountCertificatesByNamesParallel(t *testing.T) {
|
|
sa, clk, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
// Create two certs with different names and add them both to the database.
|
|
reg := createWorkingRegistration(t, sa)
|
|
|
|
_, testCert := test.ThrowAwayCert(t, clk)
|
|
_, err := sa.AddCertificate(ctx, &sapb.AddCertificateRequest{
|
|
Der: testCert.Raw,
|
|
RegID: reg.Id,
|
|
Issued: timestamppb.New(testCert.NotBefore),
|
|
})
|
|
test.AssertNotError(t, err, "Couldn't add test cert")
|
|
|
|
_, testCert2 := test.ThrowAwayCert(t, clk)
|
|
_, err = sa.AddCertificate(ctx, &sapb.AddCertificateRequest{
|
|
Der: testCert2.Raw,
|
|
RegID: reg.Id,
|
|
Issued: timestamppb.New(testCert2.NotBefore),
|
|
})
|
|
test.AssertNotError(t, err, "Couldn't add test cert")
|
|
|
|
// Override countCertificatesByName with an implementation of certCountFunc
|
|
// that will block forever if it's called in serial, but will succeed if
|
|
// called in parallel.
|
|
names := []string{"does.not.exist", testCert.DNSNames[0], testCert2.DNSNames[0]}
|
|
|
|
var interlocker sync.WaitGroup
|
|
interlocker.Add(len(names))
|
|
sa.parallelismPerRPC = len(names)
|
|
oldCertCountFunc := sa.countCertificatesByName
|
|
sa.countCertificatesByName = func(ctx context.Context, sel db.Selector, domain string, timeRange *sapb.Range) (int64, time.Time, error) {
|
|
interlocker.Done()
|
|
interlocker.Wait()
|
|
return oldCertCountFunc(ctx, sel, domain, timeRange)
|
|
}
|
|
|
|
counts, err := sa.CountCertificatesByNames(ctx, &sapb.CountCertificatesByNamesRequest{
|
|
DnsNames: names,
|
|
Range: &sapb.Range{
|
|
Earliest: timestamppb.New(clk.Now().Add(-time.Hour)),
|
|
Latest: timestamppb.New(clk.Now().Add(time.Hour)),
|
|
},
|
|
})
|
|
test.AssertNotError(t, err, "Error counting certs.")
|
|
test.AssertEquals(t, len(counts.Counts), 3)
|
|
|
|
// We expect there to be two of each of the names that do exist, because
|
|
// test.ThrowAwayCert creates certs for subdomains of example.com, and
|
|
// CountCertificatesByNames counts all certs under the same registered domain.
|
|
expected := map[string]int64{
|
|
"does.not.exist": 0,
|
|
testCert.DNSNames[0]: 2,
|
|
testCert2.DNSNames[0]: 2,
|
|
}
|
|
for name, count := range expected {
|
|
test.AssertEquals(t, count, counts.Counts[name])
|
|
}
|
|
}
|
|
|
|
func TestCountRegistrationsByIP(t *testing.T) {
|
|
sa, fc, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
contact := []string{"mailto:foo@example.com"}
|
|
|
|
// Create one IPv4 registration
|
|
key, _ := jose.JSONWebKey{Key: &rsa.PublicKey{N: big.NewInt(1), E: 1}}.MarshalJSON()
|
|
initialIP, _ := net.ParseIP("43.34.43.34").MarshalText()
|
|
_, err := sa.NewRegistration(ctx, &corepb.Registration{
|
|
Key: key,
|
|
InitialIP: initialIP,
|
|
Contact: contact,
|
|
})
|
|
// Create two IPv6 registrations, both within the same /48
|
|
key, _ = jose.JSONWebKey{Key: &rsa.PublicKey{N: big.NewInt(2), E: 1}}.MarshalJSON()
|
|
initialIP, _ = net.ParseIP("2001:cdba:1234:5678:9101:1121:3257:9652").MarshalText()
|
|
test.AssertNotError(t, err, "Couldn't insert registration")
|
|
_, err = sa.NewRegistration(ctx, &corepb.Registration{
|
|
Key: key,
|
|
InitialIP: initialIP,
|
|
Contact: contact,
|
|
})
|
|
test.AssertNotError(t, err, "Couldn't insert registration")
|
|
key, _ = jose.JSONWebKey{Key: &rsa.PublicKey{N: big.NewInt(3), E: 1}}.MarshalJSON()
|
|
initialIP, _ = net.ParseIP("2001:cdba:1234:5678:9101:1121:3257:9653").MarshalText()
|
|
_, err = sa.NewRegistration(ctx, &corepb.Registration{
|
|
Key: key,
|
|
InitialIP: initialIP,
|
|
Contact: contact,
|
|
})
|
|
test.AssertNotError(t, err, "Couldn't insert registration")
|
|
|
|
latest := fc.Now()
|
|
earliest := latest.Add(-time.Hour * 24)
|
|
req := &sapb.CountRegistrationsByIPRequest{
|
|
Ip: net.ParseIP("1.1.1.1"),
|
|
Range: &sapb.Range{
|
|
Earliest: timestamppb.New(earliest),
|
|
Latest: timestamppb.New(latest),
|
|
},
|
|
}
|
|
|
|
// There should be 0 registrations for an IPv4 address we didn't add
|
|
// a registration for
|
|
count, err := sa.CountRegistrationsByIP(ctx, req)
|
|
test.AssertNotError(t, err, "Failed to count registrations")
|
|
test.AssertEquals(t, count.Count, int64(0))
|
|
// There should be 1 registration for the IPv4 address we did add
|
|
// a registration for.
|
|
req.Ip = net.ParseIP("43.34.43.34")
|
|
count, err = sa.CountRegistrationsByIP(ctx, req)
|
|
test.AssertNotError(t, err, "Failed to count registrations")
|
|
test.AssertEquals(t, count.Count, int64(1))
|
|
// There should be 1 registration for the first IPv6 address we added
|
|
// a registration for
|
|
req.Ip = net.ParseIP("2001:cdba:1234:5678:9101:1121:3257:9652")
|
|
count, err = sa.CountRegistrationsByIP(ctx, req)
|
|
test.AssertNotError(t, err, "Failed to count registrations")
|
|
test.AssertEquals(t, count.Count, int64(1))
|
|
// There should be 1 registration for the second IPv6 address we added
|
|
// a registration for as well
|
|
req.Ip = net.ParseIP("2001:cdba:1234:5678:9101:1121:3257:9653")
|
|
count, err = sa.CountRegistrationsByIP(ctx, req)
|
|
test.AssertNotError(t, err, "Failed to count registrations")
|
|
test.AssertEquals(t, count.Count, int64(1))
|
|
// There should be 0 registrations for an IPv6 address in the same /48 as the
|
|
// two IPv6 addresses with registrations
|
|
req.Ip = net.ParseIP("2001:cdba:1234:0000:0000:0000:0000:0000")
|
|
count, err = sa.CountRegistrationsByIP(ctx, req)
|
|
test.AssertNotError(t, err, "Failed to count registrations")
|
|
test.AssertEquals(t, count.Count, int64(0))
|
|
}
|
|
|
|
func TestCountRegistrationsByIPRange(t *testing.T) {
|
|
sa, fc, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
contact := []string{"mailto:foo@example.com"}
|
|
|
|
// Create one IPv4 registration
|
|
key, _ := jose.JSONWebKey{Key: &rsa.PublicKey{N: big.NewInt(1), E: 1}}.MarshalJSON()
|
|
initialIP, _ := net.ParseIP("43.34.43.34").MarshalText()
|
|
_, err := sa.NewRegistration(ctx, &corepb.Registration{
|
|
Key: key,
|
|
InitialIP: initialIP,
|
|
Contact: contact,
|
|
})
|
|
// Create two IPv6 registrations, both within the same /48
|
|
key, _ = jose.JSONWebKey{Key: &rsa.PublicKey{N: big.NewInt(2), E: 1}}.MarshalJSON()
|
|
initialIP, _ = net.ParseIP("2001:cdba:1234:5678:9101:1121:3257:9652").MarshalText()
|
|
test.AssertNotError(t, err, "Couldn't insert registration")
|
|
_, err = sa.NewRegistration(ctx, &corepb.Registration{
|
|
Key: key,
|
|
InitialIP: initialIP,
|
|
Contact: contact,
|
|
})
|
|
test.AssertNotError(t, err, "Couldn't insert registration")
|
|
key, _ = jose.JSONWebKey{Key: &rsa.PublicKey{N: big.NewInt(3), E: 1}}.MarshalJSON()
|
|
initialIP, _ = net.ParseIP("2001:cdba:1234:5678:9101:1121:3257:9653").MarshalText()
|
|
_, err = sa.NewRegistration(ctx, &corepb.Registration{
|
|
Key: key,
|
|
InitialIP: initialIP,
|
|
Contact: contact,
|
|
})
|
|
test.AssertNotError(t, err, "Couldn't insert registration")
|
|
|
|
latest := fc.Now()
|
|
earliest := latest.Add(-time.Hour * 24)
|
|
req := &sapb.CountRegistrationsByIPRequest{
|
|
Ip: net.ParseIP("1.1.1.1"),
|
|
Range: &sapb.Range{
|
|
Earliest: timestamppb.New(earliest),
|
|
Latest: timestamppb.New(latest),
|
|
},
|
|
}
|
|
|
|
// There should be 0 registrations in the range for an IPv4 address we didn't
|
|
// add a registration for
|
|
req.Ip = net.ParseIP("1.1.1.1")
|
|
count, err := sa.CountRegistrationsByIPRange(ctx, req)
|
|
test.AssertNotError(t, err, "Failed to count registrations")
|
|
test.AssertEquals(t, count.Count, int64(0))
|
|
// There should be 1 registration in the range for the IPv4 address we did
|
|
// add a registration for
|
|
req.Ip = net.ParseIP("43.34.43.34")
|
|
count, err = sa.CountRegistrationsByIPRange(ctx, req)
|
|
test.AssertNotError(t, err, "Failed to count registrations")
|
|
test.AssertEquals(t, count.Count, int64(1))
|
|
// There should be 2 registrations in the range for the first IPv6 address we added
|
|
// a registration for because it's in the same /48
|
|
req.Ip = net.ParseIP("2001:cdba:1234:5678:9101:1121:3257:9652")
|
|
count, err = sa.CountRegistrationsByIPRange(ctx, req)
|
|
test.AssertNotError(t, err, "Failed to count registrations")
|
|
test.AssertEquals(t, count.Count, int64(2))
|
|
// There should be 2 registrations in the range for the second IPv6 address
|
|
// we added a registration for as well, because it too is in the same /48
|
|
req.Ip = net.ParseIP("2001:cdba:1234:5678:9101:1121:3257:9653")
|
|
count, err = sa.CountRegistrationsByIPRange(ctx, req)
|
|
test.AssertNotError(t, err, "Failed to count registrations")
|
|
test.AssertEquals(t, count.Count, int64(2))
|
|
// There should also be 2 registrations in the range for an arbitrary IPv6 address in
|
|
// the same /48 as the registrations we added
|
|
req.Ip = net.ParseIP("2001:cdba:1234:0000:0000:0000:0000:0000")
|
|
count, err = sa.CountRegistrationsByIPRange(ctx, req)
|
|
test.AssertNotError(t, err, "Failed to count registrations")
|
|
test.AssertEquals(t, count.Count, int64(2))
|
|
}
|
|
|
|
func TestFQDNSets(t *testing.T) {
|
|
ctx := context.Background()
|
|
sa, fc, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
tx, err := sa.dbMap.BeginTx(ctx)
|
|
test.AssertNotError(t, err, "Failed to open transaction")
|
|
names := []string{"a.example.com", "B.example.com"}
|
|
expires := fc.Now().Add(time.Hour * 2).UTC()
|
|
issued := fc.Now()
|
|
err = addFQDNSet(ctx, tx, names, "serial", issued, expires)
|
|
test.AssertNotError(t, err, "Failed to add name set")
|
|
test.AssertNotError(t, tx.Commit(), "Failed to commit transaction")
|
|
|
|
// Invalid Window
|
|
req := &sapb.CountFQDNSetsRequest{
|
|
DnsNames: names,
|
|
Window: nil,
|
|
}
|
|
_, err = sa.CountFQDNSets(ctx, req)
|
|
test.AssertErrorIs(t, err, errIncompleteRequest)
|
|
|
|
threeHours := time.Hour * 3
|
|
req = &sapb.CountFQDNSetsRequest{
|
|
DnsNames: names,
|
|
Window: durationpb.New(threeHours),
|
|
}
|
|
// only one valid
|
|
count, err := sa.CountFQDNSets(ctx, req)
|
|
test.AssertNotError(t, err, "Failed to count name sets")
|
|
test.AssertEquals(t, count.Count, int64(1))
|
|
|
|
// check hash isn't affected by changing name order/casing
|
|
req.DnsNames = []string{"b.example.com", "A.example.COM"}
|
|
count, err = sa.CountFQDNSets(ctx, req)
|
|
test.AssertNotError(t, err, "Failed to count name sets")
|
|
test.AssertEquals(t, count.Count, int64(1))
|
|
|
|
// add another valid set
|
|
tx, err = sa.dbMap.BeginTx(ctx)
|
|
test.AssertNotError(t, err, "Failed to open transaction")
|
|
err = addFQDNSet(ctx, tx, names, "anotherSerial", issued, expires)
|
|
test.AssertNotError(t, err, "Failed to add name set")
|
|
test.AssertNotError(t, tx.Commit(), "Failed to commit transaction")
|
|
|
|
// only two valid
|
|
req.DnsNames = names
|
|
count, err = sa.CountFQDNSets(ctx, req)
|
|
test.AssertNotError(t, err, "Failed to count name sets")
|
|
test.AssertEquals(t, count.Count, int64(2))
|
|
|
|
// add an expired set
|
|
tx, err = sa.dbMap.BeginTx(ctx)
|
|
test.AssertNotError(t, err, "Failed to open transaction")
|
|
err = addFQDNSet(
|
|
ctx,
|
|
tx,
|
|
names,
|
|
"yetAnotherSerial",
|
|
issued.Add(-threeHours),
|
|
expires.Add(-threeHours),
|
|
)
|
|
test.AssertNotError(t, err, "Failed to add name set")
|
|
test.AssertNotError(t, tx.Commit(), "Failed to commit transaction")
|
|
|
|
// only two valid
|
|
count, err = sa.CountFQDNSets(ctx, req)
|
|
test.AssertNotError(t, err, "Failed to count name sets")
|
|
test.AssertEquals(t, count.Count, int64(2))
|
|
}
|
|
|
|
func TestFQDNSetTimestampsForWindow(t *testing.T) {
|
|
sa, fc, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
tx, err := sa.dbMap.BeginTx(ctx)
|
|
test.AssertNotError(t, err, "Failed to open transaction")
|
|
|
|
names := []string{"a.example.com", "B.example.com"}
|
|
|
|
// Invalid Window
|
|
req := &sapb.CountFQDNSetsRequest{
|
|
DnsNames: names,
|
|
Window: nil,
|
|
}
|
|
_, err = sa.FQDNSetTimestampsForWindow(ctx, req)
|
|
test.AssertErrorIs(t, err, errIncompleteRequest)
|
|
|
|
window := time.Hour * 3
|
|
req = &sapb.CountFQDNSetsRequest{
|
|
DnsNames: names,
|
|
Window: durationpb.New(window),
|
|
}
|
|
|
|
// Ensure zero issuance has occurred for names.
|
|
resp, err := sa.FQDNSetTimestampsForWindow(ctx, req)
|
|
test.AssertNotError(t, err, "Failed to count name sets")
|
|
test.AssertEquals(t, len(resp.Timestamps), 0)
|
|
|
|
// Add an issuance for names inside the window.
|
|
expires := fc.Now().Add(time.Hour * 2).UTC()
|
|
firstIssued := fc.Now()
|
|
err = addFQDNSet(ctx, tx, names, "serial", firstIssued, expires)
|
|
test.AssertNotError(t, err, "Failed to add name set")
|
|
test.AssertNotError(t, tx.Commit(), "Failed to commit transaction")
|
|
|
|
// Ensure there's 1 issuance timestamp for names inside the window.
|
|
resp, err = sa.FQDNSetTimestampsForWindow(ctx, req)
|
|
test.AssertNotError(t, err, "Failed to count name sets")
|
|
test.AssertEquals(t, len(resp.Timestamps), 1)
|
|
test.AssertEquals(t, firstIssued, resp.Timestamps[len(resp.Timestamps)-1].AsTime())
|
|
|
|
// Ensure that the hash isn't affected by changing name order/casing.
|
|
req.DnsNames = []string{"b.example.com", "A.example.COM"}
|
|
resp, err = sa.FQDNSetTimestampsForWindow(ctx, req)
|
|
test.AssertNotError(t, err, "Failed to count name sets")
|
|
test.AssertEquals(t, len(resp.Timestamps), 1)
|
|
test.AssertEquals(t, firstIssued, resp.Timestamps[len(resp.Timestamps)-1].AsTime())
|
|
|
|
// Add another issuance for names inside the window.
|
|
tx, err = sa.dbMap.BeginTx(ctx)
|
|
test.AssertNotError(t, err, "Failed to open transaction")
|
|
err = addFQDNSet(ctx, tx, names, "anotherSerial", firstIssued, expires)
|
|
test.AssertNotError(t, err, "Failed to add name set")
|
|
test.AssertNotError(t, tx.Commit(), "Failed to commit transaction")
|
|
|
|
// Ensure there are two issuance timestamps for names inside the window.
|
|
req.DnsNames = names
|
|
resp, err = sa.FQDNSetTimestampsForWindow(ctx, req)
|
|
test.AssertNotError(t, err, "Failed to count name sets")
|
|
test.AssertEquals(t, len(resp.Timestamps), 2)
|
|
test.AssertEquals(t, firstIssued, resp.Timestamps[len(resp.Timestamps)-1].AsTime())
|
|
|
|
// Add another issuance for names but just outside the window.
|
|
tx, err = sa.dbMap.BeginTx(ctx)
|
|
test.AssertNotError(t, err, "Failed to open transaction")
|
|
err = addFQDNSet(ctx, tx, names, "yetAnotherSerial", firstIssued.Add(-window), expires)
|
|
test.AssertNotError(t, err, "Failed to add name set")
|
|
test.AssertNotError(t, tx.Commit(), "Failed to commit transaction")
|
|
|
|
// Ensure there are still only two issuance timestamps in the window.
|
|
resp, err = sa.FQDNSetTimestampsForWindow(ctx, req)
|
|
test.AssertNotError(t, err, "Failed to count name sets")
|
|
test.AssertEquals(t, len(resp.Timestamps), 2)
|
|
test.AssertEquals(t, firstIssued, resp.Timestamps[len(resp.Timestamps)-1].AsTime())
|
|
}
|
|
|
|
func TestFQDNSetsExists(t *testing.T) {
|
|
sa, fc, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
names := []string{"a.example.com", "B.example.com"}
|
|
exists, err := sa.FQDNSetExists(ctx, &sapb.FQDNSetExistsRequest{DnsNames: names})
|
|
test.AssertNotError(t, err, "Failed to check FQDN set existence")
|
|
test.Assert(t, !exists.Exists, "FQDN set shouldn't exist")
|
|
|
|
tx, err := sa.dbMap.BeginTx(ctx)
|
|
test.AssertNotError(t, err, "Failed to open transaction")
|
|
expires := fc.Now().Add(time.Hour * 2).UTC()
|
|
issued := fc.Now()
|
|
err = addFQDNSet(ctx, tx, names, "serial", issued, expires)
|
|
test.AssertNotError(t, err, "Failed to add name set")
|
|
test.AssertNotError(t, tx.Commit(), "Failed to commit transaction")
|
|
|
|
exists, err = sa.FQDNSetExists(ctx, &sapb.FQDNSetExistsRequest{DnsNames: names})
|
|
test.AssertNotError(t, err, "Failed to check FQDN set existence")
|
|
test.Assert(t, exists.Exists, "FQDN set does exist")
|
|
}
|
|
|
|
type queryRecorder struct {
|
|
query string
|
|
args []interface{}
|
|
}
|
|
|
|
func (e *queryRecorder) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
|
|
e.query = query
|
|
e.args = args
|
|
return nil, nil
|
|
}
|
|
|
|
func TestAddIssuedNames(t *testing.T) {
|
|
serial := big.NewInt(1)
|
|
expectedSerial := "000000000000000000000000000000000001"
|
|
notBefore := time.Date(2018, 2, 14, 12, 0, 0, 0, time.UTC)
|
|
expectedNotBefore := notBefore.Truncate(24 * time.Hour)
|
|
placeholdersPerName := "(?,?,?,?)"
|
|
baseQuery := "INSERT INTO issuedNames (reversedName,serial,notBefore,renewal) VALUES"
|
|
|
|
testCases := []struct {
|
|
Name string
|
|
IssuedNames []string
|
|
SerialNumber *big.Int
|
|
NotBefore time.Time
|
|
Renewal bool
|
|
ExpectedArgs []interface{}
|
|
}{
|
|
{
|
|
Name: "One domain, not a renewal",
|
|
IssuedNames: []string{"example.co.uk"},
|
|
SerialNumber: serial,
|
|
NotBefore: notBefore,
|
|
Renewal: false,
|
|
ExpectedArgs: []interface{}{
|
|
"uk.co.example",
|
|
expectedSerial,
|
|
expectedNotBefore,
|
|
false,
|
|
},
|
|
},
|
|
{
|
|
Name: "Two domains, not a renewal",
|
|
IssuedNames: []string{"example.co.uk", "example.xyz"},
|
|
SerialNumber: serial,
|
|
NotBefore: notBefore,
|
|
Renewal: false,
|
|
ExpectedArgs: []interface{}{
|
|
"uk.co.example",
|
|
expectedSerial,
|
|
expectedNotBefore,
|
|
false,
|
|
"xyz.example",
|
|
expectedSerial,
|
|
expectedNotBefore,
|
|
false,
|
|
},
|
|
},
|
|
{
|
|
Name: "One domain, renewal",
|
|
IssuedNames: []string{"example.co.uk"},
|
|
SerialNumber: serial,
|
|
NotBefore: notBefore,
|
|
Renewal: true,
|
|
ExpectedArgs: []interface{}{
|
|
"uk.co.example",
|
|
expectedSerial,
|
|
expectedNotBefore,
|
|
true,
|
|
},
|
|
},
|
|
{
|
|
Name: "Two domains, renewal",
|
|
IssuedNames: []string{"example.co.uk", "example.xyz"},
|
|
SerialNumber: serial,
|
|
NotBefore: notBefore,
|
|
Renewal: true,
|
|
ExpectedArgs: []interface{}{
|
|
"uk.co.example",
|
|
expectedSerial,
|
|
expectedNotBefore,
|
|
true,
|
|
"xyz.example",
|
|
expectedSerial,
|
|
expectedNotBefore,
|
|
true,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
var e queryRecorder
|
|
err := addIssuedNames(
|
|
ctx,
|
|
&e,
|
|
&x509.Certificate{
|
|
DNSNames: tc.IssuedNames,
|
|
SerialNumber: tc.SerialNumber,
|
|
NotBefore: tc.NotBefore,
|
|
},
|
|
tc.Renewal)
|
|
test.AssertNotError(t, err, "addIssuedNames failed")
|
|
expectedPlaceholders := placeholdersPerName
|
|
for range len(tc.IssuedNames) - 1 {
|
|
expectedPlaceholders = fmt.Sprintf("%s,%s", expectedPlaceholders, placeholdersPerName)
|
|
}
|
|
expectedQuery := fmt.Sprintf("%s %s", baseQuery, expectedPlaceholders)
|
|
test.AssertEquals(t, e.query, expectedQuery)
|
|
if !reflect.DeepEqual(e.args, tc.ExpectedArgs) {
|
|
t.Errorf("Wrong args: got\n%#v, expected\n%#v", e.args, tc.ExpectedArgs)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDeactivateAuthorization2(t *testing.T) {
|
|
sa, fc, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
// deactivate a pending authorization
|
|
expires := fc.Now().Add(time.Hour).UTC()
|
|
attemptedAt := fc.Now()
|
|
authzID := createPendingAuthorization(t, sa, "example.com", expires)
|
|
_, err := sa.DeactivateAuthorization2(context.Background(), &sapb.AuthorizationID2{Id: authzID})
|
|
test.AssertNotError(t, err, "sa.DeactivateAuthorization2 failed")
|
|
|
|
// deactivate a valid authorization"
|
|
authzID = createFinalizedAuthorization(t, sa, "example.com", expires, "valid", attemptedAt)
|
|
_, err = sa.DeactivateAuthorization2(context.Background(), &sapb.AuthorizationID2{Id: authzID})
|
|
test.AssertNotError(t, err, "sa.DeactivateAuthorization2 failed")
|
|
}
|
|
|
|
func TestDeactivateAccount(t *testing.T) {
|
|
sa, _, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
reg := createWorkingRegistration(t, sa)
|
|
|
|
_, err := sa.DeactivateRegistration(context.Background(), &sapb.RegistrationID{Id: reg.Id})
|
|
test.AssertNotError(t, err, "DeactivateRegistration failed")
|
|
|
|
dbReg, err := sa.GetRegistration(context.Background(), &sapb.RegistrationID{Id: reg.Id})
|
|
test.AssertNotError(t, err, "GetRegistration failed")
|
|
test.AssertEquals(t, core.AcmeStatus(dbReg.Status), core.StatusDeactivated)
|
|
}
|
|
|
|
func TestReverseName(t *testing.T) {
|
|
testCases := []struct {
|
|
inputDomain string
|
|
inputReversed string
|
|
}{
|
|
{"", ""},
|
|
{"...", "..."},
|
|
{"com", "com"},
|
|
{"example.com", "com.example"},
|
|
{"www.example.com", "com.example.www"},
|
|
{"world.wide.web.example.com", "com.example.web.wide.world"},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
output := ReverseName(tc.inputDomain)
|
|
test.AssertEquals(t, output, tc.inputReversed)
|
|
}
|
|
}
|
|
|
|
func TestNewOrderAndAuthzs(t *testing.T) {
|
|
sa, _, cleanup := initSA(t)
|
|
defer cleanup()
|
|
|
|
reg := createWorkingRegistration(t, sa)
|
|
|
|
// Insert two pre-existing authorizations to reference
|
|
idA := createPendingAuthorization(t, sa, "a.com", sa.clk.Now().Add(time.Hour))
|
|
idB := createPendingAuthorization(t, sa, "b.com", sa.clk.Now().Add(time.Hour))
|
|
test.AssertEquals(t, idA, int64(1))
|
|
test.AssertEquals(t, idB, int64(2))
|
|
|
|
nowC := sa.clk.Now().Add(time.Hour)
|
|
nowD := sa.clk.Now().Add(time.Hour)
|
|
expires := sa.clk.Now().Add(2 * time.Hour)
|
|
order, err := sa.NewOrderAndAuthzs(context.Background(), &sapb.NewOrderAndAuthzsRequest{
|
|
// Insert an order for four names, two of which already have authzs
|
|
NewOrder: &sapb.NewOrderRequest{
|
|
RegistrationID: reg.Id,
|
|
Expires: timestamppb.New(expires),
|
|
DnsNames: []string{"a.com", "b.com", "c.com", "d.com"},
|
|
V2Authorizations: []int64{1, 2},
|
|
},
|
|
// And add new authorizations for the other two names.
|
|
NewAuthzs: []*sapb.NewAuthzRequest{
|
|
{
|
|
Identifier: &corepb.Identifier{Type: "dns", Value: "c.com"},
|
|
RegistrationID: reg.Id,
|
|
Expires: timestamppb.New(nowC),
|
|
ChallengeTypes: []string{string(core.ChallengeTypeHTTP01)},
|
|
Token: core.NewToken(),
|
|
},
|
|
{
|
|
Identifier: &corepb.Identifier{Type: "dns", Value: "d.com"},
|
|
RegistrationID: reg.Id,
|
|
Expires: timestamppb.New(nowD),
|
|
ChallengeTypes: []string{string(core.ChallengeTypeHTTP01)},
|
|
Token: core.NewToken(),
|
|
},
|
|
},
|
|
})
|
|
test.AssertNotError(t, err, "sa.NewOrderAndAuthzs failed")
|
|
test.AssertEquals(t, order.Id, int64(1))
|
|
test.AssertDeepEquals(t, order.V2Authorizations, []int64{1, 2, 3, 4})
|
|
|
|
var authzIDs []int64
|
|
_, err = sa.dbMap.Select(ctx, &authzIDs, "SELECT authzID FROM orderToAuthz2 WHERE orderID = ?;", order.Id)
|
|
test.AssertNotError(t, err, "Failed to count orderToAuthz entries")
|
|
test.AssertEquals(t, len(authzIDs), 4)
|
|
test.AssertDeepEquals(t, authzIDs, []int64{1, 2, 3, 4})
|
|
}
|
|
|
|
// TestNewOrderAndAuthzs_NonNilInnerOrder verifies that a nil
|
|
// sapb.NewOrderAndAuthzsRequest NewOrder object returns an error.
|
|
func TestNewOrderAndAuthzs_NonNilInnerOrder(t *testing.T) {
|
|
sa, fc, cleanup := initSA(t)
|
|
defer cleanup()
|
|
|
|
reg := createWorkingRegistration(t, sa)
|
|
|
|
expires := fc.Now().Add(2 * time.Hour)
|
|
_, err := sa.NewOrderAndAuthzs(context.Background(), &sapb.NewOrderAndAuthzsRequest{
|
|
NewAuthzs: []*sapb.NewAuthzRequest{
|
|
{
|
|
Identifier: &corepb.Identifier{Type: "dns", Value: "c.com"},
|
|
RegistrationID: reg.Id,
|
|
Expires: timestamppb.New(expires),
|
|
ChallengeTypes: []string{string(core.ChallengeTypeDNS01)},
|
|
Token: core.NewToken(),
|
|
},
|
|
},
|
|
})
|
|
test.AssertErrorIs(t, err, errIncompleteRequest)
|
|
}
|
|
|
|
func TestNewOrderAndAuthzs_MismatchedRegID(t *testing.T) {
|
|
sa, _, cleanup := initSA(t)
|
|
defer cleanup()
|
|
|
|
_, err := sa.NewOrderAndAuthzs(context.Background(), &sapb.NewOrderAndAuthzsRequest{
|
|
NewOrder: &sapb.NewOrderRequest{
|
|
RegistrationID: 1,
|
|
},
|
|
NewAuthzs: []*sapb.NewAuthzRequest{
|
|
{
|
|
RegistrationID: 2,
|
|
},
|
|
},
|
|
})
|
|
test.AssertError(t, err, "mismatched regIDs should fail")
|
|
test.AssertContains(t, err.Error(), "same account")
|
|
}
|
|
|
|
func TestNewOrderAndAuthzs_NewAuthzExpectedFields(t *testing.T) {
|
|
sa, fc, cleanup := initSA(t)
|
|
defer cleanup()
|
|
|
|
reg := createWorkingRegistration(t, sa)
|
|
expires := fc.Now().Add(time.Hour)
|
|
domain := "a.com"
|
|
|
|
// Create an authz that does not yet exist in the database with some invalid
|
|
// data smuggled in.
|
|
order, err := sa.NewOrderAndAuthzs(context.Background(), &sapb.NewOrderAndAuthzsRequest{
|
|
NewAuthzs: []*sapb.NewAuthzRequest{
|
|
{
|
|
Identifier: &corepb.Identifier{Type: "dns", Value: domain},
|
|
RegistrationID: reg.Id,
|
|
Expires: timestamppb.New(expires),
|
|
ChallengeTypes: []string{string(core.ChallengeTypeHTTP01)},
|
|
Token: core.NewToken(),
|
|
},
|
|
},
|
|
NewOrder: &sapb.NewOrderRequest{
|
|
RegistrationID: reg.Id,
|
|
Expires: timestamppb.New(expires),
|
|
DnsNames: []string{domain},
|
|
},
|
|
})
|
|
test.AssertNotError(t, err, "sa.NewOrderAndAuthzs failed")
|
|
|
|
// Safely get the authz for the order we created above.
|
|
obj, err := sa.dbReadOnlyMap.Get(ctx, authzModel{}, order.V2Authorizations[0])
|
|
test.AssertNotError(t, err, fmt.Sprintf("authorization %d not found", order.V2Authorizations[0]))
|
|
|
|
// To access the data stored in obj at compile time, we type assert obj
|
|
// into a pointer to an authzModel.
|
|
am, ok := obj.(*authzModel)
|
|
test.Assert(t, ok, "Could not type assert obj into authzModel")
|
|
|
|
// If we're making a brand new authz, it should have the pending status
|
|
// regardless of what incorrect status value was passed in during construction.
|
|
test.AssertEquals(t, am.Status, statusUint(core.StatusPending))
|
|
|
|
// Testing for the existence of these boxed nils is a definite break from
|
|
// our paradigm of avoiding passing around boxed nils whenever possible.
|
|
// However, the existence of these boxed nils in relation to this test is
|
|
// actually expected. If these tests fail, then a possible SA refactor or RA
|
|
// bug placed incorrect data into brand new authz input fields.
|
|
test.AssertBoxedNil(t, am.Attempted, "am.Attempted should be nil")
|
|
test.AssertBoxedNil(t, am.AttemptedAt, "am.AttemptedAt should be nil")
|
|
test.AssertBoxedNil(t, am.ValidationError, "am.ValidationError should be nil")
|
|
test.AssertBoxedNil(t, am.ValidationRecord, "am.ValidationRecord should be nil")
|
|
}
|
|
|
|
func TestSetOrderProcessing(t *testing.T) {
|
|
sa, fc, cleanup := initSA(t)
|
|
defer cleanup()
|
|
|
|
reg := createWorkingRegistration(t, sa)
|
|
|
|
// Add one valid authz
|
|
expires := fc.Now().Add(time.Hour)
|
|
attemptedAt := fc.Now()
|
|
authzID := createFinalizedAuthorization(t, sa, "example.com", expires, "valid", attemptedAt)
|
|
|
|
// Add a new order in pending status with no certificate serial
|
|
expires1Year := sa.clk.Now().Add(365 * 24 * time.Hour)
|
|
order, err := sa.NewOrderAndAuthzs(context.Background(), &sapb.NewOrderAndAuthzsRequest{
|
|
NewOrder: &sapb.NewOrderRequest{
|
|
RegistrationID: reg.Id,
|
|
Expires: timestamppb.New(expires1Year),
|
|
DnsNames: []string{"example.com"},
|
|
V2Authorizations: []int64{authzID},
|
|
},
|
|
})
|
|
test.AssertNotError(t, err, "NewOrderAndAuthzs failed")
|
|
|
|
// Set the order to be processing
|
|
_, err = sa.SetOrderProcessing(context.Background(), &sapb.OrderRequest{Id: order.Id})
|
|
test.AssertNotError(t, err, "SetOrderProcessing failed")
|
|
|
|
// Read the order by ID from the DB to check the status was correctly updated
|
|
// to processing
|
|
updatedOrder, err := sa.GetOrder(
|
|
context.Background(),
|
|
&sapb.OrderRequest{Id: order.Id})
|
|
test.AssertNotError(t, err, "GetOrder failed")
|
|
test.AssertEquals(t, updatedOrder.Status, string(core.StatusProcessing))
|
|
test.AssertEquals(t, updatedOrder.BeganProcessing, true)
|
|
|
|
// Try to set the same order to be processing again. We should get an error.
|
|
_, err = sa.SetOrderProcessing(context.Background(), &sapb.OrderRequest{Id: order.Id})
|
|
test.AssertError(t, err, "Set the same order processing twice. This should have been an error.")
|
|
test.AssertErrorIs(t, err, berrors.OrderNotReady)
|
|
}
|
|
|
|
func TestFinalizeOrder(t *testing.T) {
|
|
sa, fc, cleanup := initSA(t)
|
|
defer cleanup()
|
|
|
|
reg := createWorkingRegistration(t, sa)
|
|
expires := fc.Now().Add(time.Hour)
|
|
attemptedAt := fc.Now()
|
|
authzID := createFinalizedAuthorization(t, sa, "example.com", expires, "valid", attemptedAt)
|
|
|
|
// Add a new order in pending status with no certificate serial
|
|
expires1Year := sa.clk.Now().Add(365 * 24 * time.Hour)
|
|
order, err := sa.NewOrderAndAuthzs(context.Background(), &sapb.NewOrderAndAuthzsRequest{
|
|
NewOrder: &sapb.NewOrderRequest{
|
|
RegistrationID: reg.Id,
|
|
Expires: timestamppb.New(expires1Year),
|
|
DnsNames: []string{"example.com"},
|
|
V2Authorizations: []int64{authzID},
|
|
},
|
|
})
|
|
test.AssertNotError(t, err, "NewOrderAndAuthzs failed")
|
|
|
|
// Set the order to processing so it can be finalized
|
|
_, err = sa.SetOrderProcessing(ctx, &sapb.OrderRequest{Id: order.Id})
|
|
test.AssertNotError(t, err, "SetOrderProcessing failed")
|
|
|
|
// Finalize the order with a certificate serial
|
|
order.CertificateSerial = "eat.serial.for.breakfast"
|
|
_, err = sa.FinalizeOrder(context.Background(), &sapb.FinalizeOrderRequest{Id: order.Id, CertificateSerial: order.CertificateSerial})
|
|
test.AssertNotError(t, err, "FinalizeOrder failed")
|
|
|
|
// Read the order by ID from the DB to check the certificate serial and status
|
|
// was correctly updated
|
|
updatedOrder, err := sa.GetOrder(
|
|
context.Background(),
|
|
&sapb.OrderRequest{Id: order.Id})
|
|
test.AssertNotError(t, err, "GetOrder failed")
|
|
test.AssertEquals(t, updatedOrder.CertificateSerial, "eat.serial.for.breakfast")
|
|
test.AssertEquals(t, updatedOrder.Status, string(core.StatusValid))
|
|
}
|
|
|
|
func TestOrderWithOrderModelv1(t *testing.T) {
|
|
sa, fc, cleanup := initSA(t)
|
|
defer cleanup()
|
|
|
|
reg := createWorkingRegistration(t, sa)
|
|
authzExpires := fc.Now().Add(time.Hour)
|
|
authzID := createPendingAuthorization(t, sa, "example.com", authzExpires)
|
|
|
|
// Set the order to expire in two hours
|
|
expires := fc.Now().Add(2 * time.Hour)
|
|
|
|
inputOrder := &corepb.Order{
|
|
RegistrationID: reg.Id,
|
|
Expires: timestamppb.New(expires),
|
|
DnsNames: []string{"example.com"},
|
|
V2Authorizations: []int64{authzID},
|
|
}
|
|
|
|
// Create the order
|
|
order, err := sa.NewOrderAndAuthzs(context.Background(), &sapb.NewOrderAndAuthzsRequest{
|
|
NewOrder: &sapb.NewOrderRequest{
|
|
RegistrationID: inputOrder.RegistrationID,
|
|
Expires: inputOrder.Expires,
|
|
DnsNames: inputOrder.DnsNames,
|
|
V2Authorizations: inputOrder.V2Authorizations,
|
|
},
|
|
})
|
|
test.AssertNotError(t, err, "sa.NewOrderAndAuthzs failed")
|
|
|
|
// The Order from GetOrder should match the following expected order
|
|
created := sa.clk.Now()
|
|
expectedOrder := &corepb.Order{
|
|
// The registration ID, authorizations, expiry, and names should match the
|
|
// input to NewOrderAndAuthzs
|
|
RegistrationID: inputOrder.RegistrationID,
|
|
V2Authorizations: inputOrder.V2Authorizations,
|
|
DnsNames: inputOrder.DnsNames,
|
|
Expires: inputOrder.Expires,
|
|
// The ID should have been set to 1 by the SA
|
|
Id: 1,
|
|
// The status should be pending
|
|
Status: string(core.StatusPending),
|
|
// The serial should be empty since this is a pending order
|
|
CertificateSerial: "",
|
|
// We should not be processing it
|
|
BeganProcessing: false,
|
|
// The created timestamp should have been set to the current time
|
|
Created: timestamppb.New(created),
|
|
}
|
|
|
|
// Fetch the order by its ID and make sure it matches the expected
|
|
storedOrder, err := sa.GetOrder(context.Background(), &sapb.OrderRequest{Id: order.Id})
|
|
test.AssertNotError(t, err, "sa.GetOrder failed")
|
|
test.AssertDeepEquals(t, storedOrder, expectedOrder)
|
|
}
|
|
|
|
func TestOrderWithOrderModelv2(t *testing.T) {
|
|
if !strings.Contains(os.Getenv("BOULDER_CONFIG_DIR"), "test/config-next") {
|
|
t.Skip()
|
|
}
|
|
|
|
// The feature must be set before the SA is constructed because of a
|
|
// conditional on this feature in //sa/database.go.
|
|
features.Set(features.Config{MultipleCertificateProfiles: true})
|
|
defer features.Reset()
|
|
|
|
fc := clock.NewFake()
|
|
fc.Set(time.Date(2015, 3, 4, 5, 0, 0, 0, time.UTC))
|
|
|
|
dbMap, err := DBMapForTest(vars.DBConnSA)
|
|
test.AssertNotError(t, err, "Couldn't create dbMap")
|
|
|
|
saro, err := NewSQLStorageAuthorityRO(dbMap, nil, metrics.NoopRegisterer, 1, 0, fc, log)
|
|
test.AssertNotError(t, err, "Couldn't create SARO")
|
|
|
|
sa, err := NewSQLStorageAuthorityWrapping(saro, dbMap, metrics.NoopRegisterer)
|
|
test.AssertNotError(t, err, "Couldn't create SA")
|
|
defer test.ResetBoulderTestDatabase(t)
|
|
|
|
// Create a test registration to reference
|
|
reg := createWorkingRegistration(t, sa)
|
|
authzExpires := fc.Now().Add(time.Hour)
|
|
authzID := createPendingAuthorization(t, sa, "example.com", authzExpires)
|
|
|
|
// Set the order to expire in two hours
|
|
expires := fc.Now().Add(2 * time.Hour)
|
|
|
|
inputOrder := &corepb.Order{
|
|
RegistrationID: reg.Id,
|
|
Expires: timestamppb.New(expires),
|
|
DnsNames: []string{"example.com"},
|
|
V2Authorizations: []int64{authzID},
|
|
CertificateProfileName: "tbiapb",
|
|
}
|
|
|
|
// Create the order
|
|
order, err := sa.NewOrderAndAuthzs(context.Background(), &sapb.NewOrderAndAuthzsRequest{
|
|
NewOrder: &sapb.NewOrderRequest{
|
|
RegistrationID: inputOrder.RegistrationID,
|
|
Expires: inputOrder.Expires,
|
|
DnsNames: inputOrder.DnsNames,
|
|
V2Authorizations: inputOrder.V2Authorizations,
|
|
CertificateProfileName: inputOrder.CertificateProfileName,
|
|
},
|
|
})
|
|
test.AssertNotError(t, err, "sa.NewOrderAndAuthzs failed")
|
|
|
|
// The Order from GetOrder should match the following expected order
|
|
created := sa.clk.Now()
|
|
expectedOrder := &corepb.Order{
|
|
// The registration ID, authorizations, expiry, and names should match the
|
|
// input to NewOrderAndAuthzs
|
|
RegistrationID: inputOrder.RegistrationID,
|
|
V2Authorizations: inputOrder.V2Authorizations,
|
|
DnsNames: inputOrder.DnsNames,
|
|
Expires: inputOrder.Expires,
|
|
// The ID should have been set to 1 by the SA
|
|
Id: 1,
|
|
// The status should be pending
|
|
Status: string(core.StatusPending),
|
|
// The serial should be empty since this is a pending order
|
|
CertificateSerial: "",
|
|
// We should not be processing it
|
|
BeganProcessing: false,
|
|
// The created timestamp should have been set to the current time
|
|
Created: timestamppb.New(created),
|
|
CertificateProfileName: "tbiapb",
|
|
}
|
|
|
|
// Fetch the order by its ID and make sure it matches the expected
|
|
storedOrder, err := sa.GetOrder(context.Background(), &sapb.OrderRequest{Id: order.Id})
|
|
test.AssertNotError(t, err, "sa.GetOrder failed")
|
|
test.AssertDeepEquals(t, storedOrder, expectedOrder)
|
|
|
|
//
|
|
// Test that an order without a certificate profile name, but with the
|
|
// MultipleCertificateProfiles feature flag enabled works as expected.
|
|
//
|
|
|
|
inputOrderNoName := &corepb.Order{
|
|
RegistrationID: reg.Id,
|
|
Expires: timestamppb.New(expires),
|
|
DnsNames: []string{"example.com"},
|
|
V2Authorizations: []int64{authzID},
|
|
}
|
|
|
|
// Create the order
|
|
orderNoName, err := sa.NewOrderAndAuthzs(context.Background(), &sapb.NewOrderAndAuthzsRequest{
|
|
NewOrder: &sapb.NewOrderRequest{
|
|
RegistrationID: inputOrderNoName.RegistrationID,
|
|
Expires: inputOrderNoName.Expires,
|
|
DnsNames: inputOrderNoName.DnsNames,
|
|
V2Authorizations: inputOrderNoName.V2Authorizations,
|
|
CertificateProfileName: inputOrderNoName.CertificateProfileName,
|
|
},
|
|
})
|
|
test.AssertNotError(t, err, "sa.NewOrderAndAuthzs failed")
|
|
|
|
// The Order from GetOrder should match the following expected order
|
|
created = sa.clk.Now()
|
|
expectedOrderNoName := &corepb.Order{
|
|
// The registration ID, authorizations, expiry, and names should match the
|
|
// input to NewOrderAndAuthzs
|
|
RegistrationID: inputOrderNoName.RegistrationID,
|
|
V2Authorizations: inputOrderNoName.V2Authorizations,
|
|
DnsNames: inputOrderNoName.DnsNames,
|
|
Expires: inputOrderNoName.Expires,
|
|
// The ID should have been set to 2 by the SA
|
|
Id: 2,
|
|
// The status should be pending
|
|
Status: string(core.StatusPending),
|
|
// The serial should be empty since this is a pending order
|
|
CertificateSerial: "",
|
|
// We should not be processing it
|
|
BeganProcessing: false,
|
|
// The created timestamp should have been set to the current time
|
|
Created: timestamppb.New(created),
|
|
}
|
|
|
|
// Fetch the order by its ID and make sure it matches the expected
|
|
storedOrderNoName, err := sa.GetOrder(context.Background(), &sapb.OrderRequest{Id: orderNoName.Id})
|
|
test.AssertNotError(t, err, "sa.GetOrder failed")
|
|
test.AssertDeepEquals(t, storedOrderNoName, expectedOrderNoName)
|
|
}
|
|
|
|
// TestGetAuthorization2NoRows ensures that the GetAuthorization2 function returns
|
|
// the correct error when there are no results for the provided ID.
|
|
func TestGetAuthorization2NoRows(t *testing.T) {
|
|
sa, _, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
// An empty authz ID should result in a not found berror.
|
|
id := int64(123)
|
|
_, err := sa.GetAuthorization2(ctx, &sapb.AuthorizationID2{Id: id})
|
|
test.AssertError(t, err, "Didn't get an error looking up non-existent authz ID")
|
|
test.AssertErrorIs(t, err, berrors.NotFound)
|
|
}
|
|
|
|
func TestGetAuthorizations2(t *testing.T) {
|
|
sa, fc, cleanup := initSA(t)
|
|
defer cleanup()
|
|
|
|
reg := createWorkingRegistration(t, sa)
|
|
exp := fc.Now().AddDate(0, 0, 10).UTC()
|
|
attemptedAt := fc.Now()
|
|
|
|
identA := "aaa"
|
|
identB := "bbb"
|
|
identC := "ccc"
|
|
identD := "ddd"
|
|
idents := []string{identA, identB, identC}
|
|
|
|
authzIDA := createFinalizedAuthorization(t, sa, "aaa", exp, "valid", attemptedAt)
|
|
authzIDB := createPendingAuthorization(t, sa, "bbb", exp)
|
|
nearbyExpires := fc.Now().UTC().Add(time.Hour)
|
|
authzIDC := createPendingAuthorization(t, sa, "ccc", nearbyExpires)
|
|
|
|
// Associate authorizations with an order so that GetAuthorizations2 thinks
|
|
// they are WFE2 authorizations.
|
|
err := sa.dbMap.Insert(ctx, &orderToAuthzModel{
|
|
OrderID: 1,
|
|
AuthzID: authzIDA,
|
|
})
|
|
test.AssertNotError(t, err, "sa.dbMap.Insert failed")
|
|
err = sa.dbMap.Insert(ctx, &orderToAuthzModel{
|
|
OrderID: 1,
|
|
AuthzID: authzIDB,
|
|
})
|
|
test.AssertNotError(t, err, "sa.dbMap.Insert failed")
|
|
err = sa.dbMap.Insert(ctx, &orderToAuthzModel{
|
|
OrderID: 1,
|
|
AuthzID: authzIDC,
|
|
})
|
|
test.AssertNotError(t, err, "sa.dbMap.Insert failed")
|
|
|
|
// Set an expiry cut off of 1 day in the future similar to `RA.NewOrderAndAuthzs`. This
|
|
// should exclude pending authorization C based on its nearbyExpires expiry
|
|
// value.
|
|
expiryCutoff := fc.Now().AddDate(0, 0, 1)
|
|
// Get authorizations for the names used above.
|
|
authz, err := sa.GetAuthorizations2(context.Background(), &sapb.GetAuthorizationsRequest{
|
|
RegistrationID: reg.Id,
|
|
DnsNames: idents,
|
|
ValidUntil: timestamppb.New(expiryCutoff),
|
|
})
|
|
// It should not fail
|
|
test.AssertNotError(t, err, "sa.GetAuthorizations2 failed")
|
|
// We should get back two authorizations since one of the three authorizations
|
|
// created above expires too soon.
|
|
test.AssertEquals(t, len(authz.Authzs), 2)
|
|
|
|
// Get authorizations for the names used above, and one name that doesn't exist
|
|
authz, err = sa.GetAuthorizations2(context.Background(), &sapb.GetAuthorizationsRequest{
|
|
RegistrationID: reg.Id,
|
|
DnsNames: append(idents, identD),
|
|
ValidUntil: timestamppb.New(expiryCutoff),
|
|
})
|
|
// It should not fail
|
|
test.AssertNotError(t, err, "sa.GetAuthorizations2 failed")
|
|
// It should still return only two authorizations
|
|
test.AssertEquals(t, len(authz.Authzs), 2)
|
|
}
|
|
|
|
func TestCountOrders(t *testing.T) {
|
|
sa, _, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
reg := createWorkingRegistration(t, sa)
|
|
now := sa.clk.Now()
|
|
expires := now.Add(24 * time.Hour)
|
|
|
|
req := &sapb.CountOrdersRequest{
|
|
AccountID: 12345,
|
|
Range: &sapb.Range{
|
|
Earliest: timestamppb.New(now.Add(-time.Hour)),
|
|
Latest: timestamppb.New(now.Add(time.Second)),
|
|
},
|
|
}
|
|
|
|
// Counting new orders for a reg ID that doesn't exist should return 0
|
|
count, err := sa.CountOrders(ctx, req)
|
|
test.AssertNotError(t, err, "Couldn't count new orders for fake reg ID")
|
|
test.AssertEquals(t, count.Count, int64(0))
|
|
|
|
// Add a pending authorization
|
|
authzID := createPendingAuthorization(t, sa, "example.com", expires)
|
|
|
|
// Add one pending order
|
|
order, err := sa.NewOrderAndAuthzs(ctx, &sapb.NewOrderAndAuthzsRequest{
|
|
NewOrder: &sapb.NewOrderRequest{
|
|
RegistrationID: reg.Id,
|
|
Expires: timestamppb.New(expires),
|
|
DnsNames: []string{"example.com"},
|
|
V2Authorizations: []int64{authzID},
|
|
},
|
|
})
|
|
test.AssertNotError(t, err, "Couldn't create new pending order")
|
|
|
|
// Counting new orders for the reg ID should now yield 1
|
|
req.AccountID = reg.Id
|
|
count, err = sa.CountOrders(ctx, req)
|
|
test.AssertNotError(t, err, "Couldn't count new orders for reg ID")
|
|
test.AssertEquals(t, count.Count, int64(1))
|
|
|
|
// Moving the count window to after the order was created should return the
|
|
// count to 0
|
|
earliest := order.Created.AsTime().Add(time.Minute)
|
|
latest := earliest.Add(time.Hour)
|
|
req.Range.Earliest = timestamppb.New(earliest)
|
|
req.Range.Latest = timestamppb.New(latest)
|
|
count, err = sa.CountOrders(ctx, req)
|
|
test.AssertNotError(t, err, "Couldn't count new orders for reg ID")
|
|
test.AssertEquals(t, count.Count, int64(0))
|
|
}
|
|
|
|
func TestFasterGetOrderForNames(t *testing.T) {
|
|
sa, fc, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
domain := "example.com"
|
|
expires := fc.Now().Add(time.Hour)
|
|
|
|
key, _ := goodTestJWK().MarshalJSON()
|
|
initialIP, _ := net.ParseIP("42.42.42.42").MarshalText()
|
|
reg, err := sa.NewRegistration(ctx, &corepb.Registration{
|
|
Key: key,
|
|
InitialIP: initialIP,
|
|
})
|
|
test.AssertNotError(t, err, "Couldn't create test registration")
|
|
|
|
authzIDs := createPendingAuthorization(t, sa, domain, expires)
|
|
|
|
_, err = sa.NewOrderAndAuthzs(ctx, &sapb.NewOrderAndAuthzsRequest{
|
|
NewOrder: &sapb.NewOrderRequest{
|
|
RegistrationID: reg.Id,
|
|
Expires: timestamppb.New(expires),
|
|
V2Authorizations: []int64{authzIDs},
|
|
DnsNames: []string{domain},
|
|
},
|
|
})
|
|
test.AssertNotError(t, err, "sa.NewOrderAndAuthzs failed")
|
|
|
|
_, err = sa.NewOrderAndAuthzs(ctx, &sapb.NewOrderAndAuthzsRequest{
|
|
NewOrder: &sapb.NewOrderRequest{
|
|
RegistrationID: reg.Id,
|
|
Expires: timestamppb.New(expires),
|
|
V2Authorizations: []int64{authzIDs},
|
|
DnsNames: []string{domain},
|
|
},
|
|
})
|
|
test.AssertNotError(t, err, "sa.NewOrderAndAuthzs failed")
|
|
|
|
_, err = sa.GetOrderForNames(ctx, &sapb.GetOrderForNamesRequest{
|
|
AcctID: reg.Id,
|
|
DnsNames: []string{domain},
|
|
})
|
|
test.AssertNotError(t, err, "sa.GetOrderForNames failed")
|
|
}
|
|
|
|
func TestGetOrderForNames(t *testing.T) {
|
|
sa, fc, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
// Give the order we create a short lifetime
|
|
orderLifetime := time.Hour
|
|
expires := fc.Now().Add(orderLifetime)
|
|
|
|
// Create two test registrations to associate with orders
|
|
key, _ := goodTestJWK().MarshalJSON()
|
|
initialIP, _ := net.ParseIP("42.42.42.42").MarshalText()
|
|
regA, err := sa.NewRegistration(ctx, &corepb.Registration{
|
|
Key: key,
|
|
InitialIP: initialIP,
|
|
})
|
|
test.AssertNotError(t, err, "Couldn't create test registration")
|
|
|
|
// Add one pending authz for the first name for regA and one
|
|
// pending authz for the second name for regA
|
|
authzExpires := fc.Now().Add(time.Hour)
|
|
authzIDA := createPendingAuthorization(t, sa, "example.com", authzExpires)
|
|
authzIDB := createPendingAuthorization(t, sa, "just.another.example.com", authzExpires)
|
|
|
|
ctx := context.Background()
|
|
names := []string{"example.com", "just.another.example.com"}
|
|
|
|
// Call GetOrderForNames for a set of names we haven't created an order for
|
|
// yet
|
|
result, err := sa.GetOrderForNames(ctx, &sapb.GetOrderForNamesRequest{
|
|
AcctID: regA.Id,
|
|
DnsNames: names,
|
|
})
|
|
// We expect the result to return an error
|
|
test.AssertError(t, err, "sa.GetOrderForNames did not return an error for an empty result")
|
|
// The error should be a notfound error
|
|
test.AssertErrorIs(t, err, berrors.NotFound)
|
|
// The result should be nil
|
|
test.Assert(t, result == nil, "sa.GetOrderForNames for non-existent order returned non-nil result")
|
|
|
|
// Add a new order for a set of names
|
|
order, err := sa.NewOrderAndAuthzs(ctx, &sapb.NewOrderAndAuthzsRequest{
|
|
NewOrder: &sapb.NewOrderRequest{
|
|
RegistrationID: regA.Id,
|
|
Expires: timestamppb.New(expires),
|
|
V2Authorizations: []int64{authzIDA, authzIDB},
|
|
DnsNames: names,
|
|
},
|
|
})
|
|
// It shouldn't error
|
|
test.AssertNotError(t, err, "sa.NewOrderAndAuthzs failed")
|
|
// The order ID shouldn't be nil
|
|
test.AssertNotNil(t, order.Id, "NewOrderAndAuthzs returned with a nil Id")
|
|
|
|
// Call GetOrderForNames with the same account ID and set of names as the
|
|
// above NewOrderAndAuthzs call
|
|
result, err = sa.GetOrderForNames(ctx, &sapb.GetOrderForNamesRequest{
|
|
AcctID: regA.Id,
|
|
DnsNames: names,
|
|
})
|
|
// It shouldn't error
|
|
test.AssertNotError(t, err, "sa.GetOrderForNames failed")
|
|
// The order returned should have the same ID as the order we created above
|
|
test.AssertNotNil(t, result, "Returned order was nil")
|
|
test.AssertEquals(t, result.Id, order.Id)
|
|
|
|
// Call GetOrderForNames with a different account ID from the NewOrderAndAuthzs call
|
|
regB := int64(1337)
|
|
result, err = sa.GetOrderForNames(ctx, &sapb.GetOrderForNamesRequest{
|
|
AcctID: regB,
|
|
DnsNames: names,
|
|
})
|
|
// It should error
|
|
test.AssertError(t, err, "sa.GetOrderForNames did not return an error for an empty result")
|
|
// The error should be a notfound error
|
|
test.AssertErrorIs(t, err, berrors.NotFound)
|
|
// The result should be nil
|
|
test.Assert(t, result == nil, "sa.GetOrderForNames for diff AcctID returned non-nil result")
|
|
|
|
// Advance the clock beyond the initial order's lifetime
|
|
fc.Add(2 * orderLifetime)
|
|
|
|
// Call GetOrderForNames again with the same account ID and set of names as
|
|
// the initial NewOrderAndAuthzs call
|
|
result, err = sa.GetOrderForNames(ctx, &sapb.GetOrderForNamesRequest{
|
|
AcctID: regA.Id,
|
|
DnsNames: names,
|
|
})
|
|
// It should error since there is no result
|
|
test.AssertError(t, err, "sa.GetOrderForNames did not return an error for an empty result")
|
|
// The error should be a notfound error
|
|
test.AssertErrorIs(t, err, berrors.NotFound)
|
|
// The result should be nil because the initial order expired & we don't want
|
|
// to return expired orders
|
|
test.Assert(t, result == nil, "sa.GetOrderForNames returned non-nil result for expired order case")
|
|
|
|
// Create two valid authorizations
|
|
authzExpires = fc.Now().Add(time.Hour)
|
|
attemptedAt := fc.Now()
|
|
authzIDC := createFinalizedAuthorization(t, sa, "zombo.com", authzExpires, "valid", attemptedAt)
|
|
authzIDD := createFinalizedAuthorization(t, sa, "welcome.to.zombo.com", authzExpires, "valid", attemptedAt)
|
|
|
|
// Add a fresh order that uses the authorizations created above
|
|
names = []string{"zombo.com", "welcome.to.zombo.com"}
|
|
expires = fc.Now().Add(orderLifetime)
|
|
order, err = sa.NewOrderAndAuthzs(ctx, &sapb.NewOrderAndAuthzsRequest{
|
|
NewOrder: &sapb.NewOrderRequest{
|
|
RegistrationID: regA.Id,
|
|
Expires: timestamppb.New(expires),
|
|
V2Authorizations: []int64{authzIDC, authzIDD},
|
|
DnsNames: names,
|
|
},
|
|
})
|
|
// It shouldn't error
|
|
test.AssertNotError(t, err, "sa.NewOrderAndAuthzs failed")
|
|
// The order ID shouldn't be nil
|
|
test.AssertNotNil(t, order.Id, "NewOrderAndAuthzs returned with a nil Id")
|
|
|
|
// Call GetOrderForNames with the same account ID and set of names as
|
|
// the earlier NewOrderAndAuthzs call
|
|
result, err = sa.GetOrderForNames(ctx, &sapb.GetOrderForNamesRequest{
|
|
AcctID: regA.Id,
|
|
DnsNames: names,
|
|
})
|
|
// It should not error since a ready order can be reused.
|
|
test.AssertNotError(t, err, "sa.GetOrderForNames returned an unexpected error for ready order reuse")
|
|
// The order returned should have the same ID as the order we created above
|
|
test.AssertNotNil(t, result, "sa.GetOrderForNames returned nil result")
|
|
test.AssertEquals(t, result.Id, order.Id)
|
|
|
|
// Set the order processing so it can be finalized
|
|
_, err = sa.SetOrderProcessing(ctx, &sapb.OrderRequest{Id: order.Id})
|
|
test.AssertNotError(t, err, "sa.SetOrderProcessing failed")
|
|
|
|
// Finalize the order
|
|
order.CertificateSerial = "cinnamon toast crunch"
|
|
_, err = sa.FinalizeOrder(ctx, &sapb.FinalizeOrderRequest{Id: order.Id, CertificateSerial: order.CertificateSerial})
|
|
test.AssertNotError(t, err, "sa.FinalizeOrder failed")
|
|
|
|
// Call GetOrderForNames with the same account ID and set of names as
|
|
// the earlier NewOrderAndAuthzs call
|
|
result, err = sa.GetOrderForNames(ctx, &sapb.GetOrderForNamesRequest{
|
|
AcctID: regA.Id,
|
|
DnsNames: names,
|
|
})
|
|
// It should error since a valid order should not be reused.
|
|
test.AssertError(t, err, "sa.GetOrderForNames did not return an error for an empty result")
|
|
// The error should be a notfound error
|
|
test.AssertErrorIs(t, err, berrors.NotFound)
|
|
// The result should be nil because the one matching order has been finalized
|
|
// already
|
|
test.Assert(t, result == nil, "sa.GetOrderForNames returned non-nil result for finalized order case")
|
|
}
|
|
|
|
func TestStatusForOrder(t *testing.T) {
|
|
sa, fc, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
ctx := context.Background()
|
|
expires := fc.Now().Add(time.Hour)
|
|
alreadyExpired := expires.Add(-2 * time.Hour)
|
|
attemptedAt := fc.Now()
|
|
|
|
// Create a registration to work with
|
|
reg := createWorkingRegistration(t, sa)
|
|
|
|
// Create a pending authz, an expired authz, an invalid authz, a deactivated authz,
|
|
// and a valid authz
|
|
pendingID := createPendingAuthorization(t, sa, "pending.your.order.is.up", expires)
|
|
expiredID := createPendingAuthorization(t, sa, "expired.your.order.is.up", alreadyExpired)
|
|
invalidID := createFinalizedAuthorization(t, sa, "invalid.your.order.is.up", expires, "invalid", attemptedAt)
|
|
validID := createFinalizedAuthorization(t, sa, "valid.your.order.is.up", expires, "valid", attemptedAt)
|
|
deactivatedID := createPendingAuthorization(t, sa, "deactivated.your.order.is.up", expires)
|
|
_, err := sa.DeactivateAuthorization2(context.Background(), &sapb.AuthorizationID2{Id: deactivatedID})
|
|
test.AssertNotError(t, err, "sa.DeactivateAuthorization2 failed")
|
|
|
|
testCases := []struct {
|
|
Name string
|
|
AuthorizationIDs []int64
|
|
OrderNames []string
|
|
OrderExpires *timestamppb.Timestamp
|
|
ExpectedStatus string
|
|
SetProcessing bool
|
|
Finalize bool
|
|
}{
|
|
{
|
|
Name: "Order with an invalid authz",
|
|
OrderNames: []string{"pending.your.order.is.up", "invalid.your.order.is.up", "deactivated.your.order.is.up", "valid.your.order.is.up"},
|
|
AuthorizationIDs: []int64{pendingID, invalidID, deactivatedID, validID},
|
|
ExpectedStatus: string(core.StatusInvalid),
|
|
},
|
|
{
|
|
Name: "Order with an expired authz",
|
|
OrderNames: []string{"pending.your.order.is.up", "expired.your.order.is.up", "deactivated.your.order.is.up", "valid.your.order.is.up"},
|
|
AuthorizationIDs: []int64{pendingID, expiredID, deactivatedID, validID},
|
|
ExpectedStatus: string(core.StatusInvalid),
|
|
},
|
|
{
|
|
Name: "Order with a deactivated authz",
|
|
OrderNames: []string{"pending.your.order.is.up", "deactivated.your.order.is.up", "valid.your.order.is.up"},
|
|
AuthorizationIDs: []int64{pendingID, deactivatedID, validID},
|
|
ExpectedStatus: string(core.StatusInvalid),
|
|
},
|
|
{
|
|
Name: "Order with a pending authz",
|
|
OrderNames: []string{"valid.your.order.is.up", "pending.your.order.is.up"},
|
|
AuthorizationIDs: []int64{validID, pendingID},
|
|
ExpectedStatus: string(core.StatusPending),
|
|
},
|
|
{
|
|
Name: "Order with only valid authzs, not yet processed or finalized",
|
|
OrderNames: []string{"valid.your.order.is.up"},
|
|
AuthorizationIDs: []int64{validID},
|
|
ExpectedStatus: string(core.StatusReady),
|
|
},
|
|
{
|
|
Name: "Order with only valid authzs, set processing",
|
|
OrderNames: []string{"valid.your.order.is.up"},
|
|
AuthorizationIDs: []int64{validID},
|
|
SetProcessing: true,
|
|
ExpectedStatus: string(core.StatusProcessing),
|
|
},
|
|
{
|
|
Name: "Order with only valid authzs, not yet processed or finalized, OrderReadyStatus feature flag",
|
|
OrderNames: []string{"valid.your.order.is.up"},
|
|
AuthorizationIDs: []int64{validID},
|
|
ExpectedStatus: string(core.StatusReady),
|
|
},
|
|
{
|
|
Name: "Order with only valid authzs, set processing",
|
|
OrderNames: []string{"valid.your.order.is.up"},
|
|
AuthorizationIDs: []int64{validID},
|
|
SetProcessing: true,
|
|
ExpectedStatus: string(core.StatusProcessing),
|
|
},
|
|
{
|
|
Name: "Order with only valid authzs, set processing and finalized",
|
|
OrderNames: []string{"valid.your.order.is.up"},
|
|
AuthorizationIDs: []int64{validID},
|
|
SetProcessing: true,
|
|
Finalize: true,
|
|
ExpectedStatus: string(core.StatusValid),
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
// If the testcase doesn't specify an order expiry use a default timestamp
|
|
// in the near future.
|
|
orderExpiry := tc.OrderExpires
|
|
if !orderExpiry.IsValid() {
|
|
orderExpiry = timestamppb.New(expires)
|
|
}
|
|
|
|
newOrder, err := sa.NewOrderAndAuthzs(ctx, &sapb.NewOrderAndAuthzsRequest{
|
|
NewOrder: &sapb.NewOrderRequest{
|
|
RegistrationID: reg.Id,
|
|
Expires: orderExpiry,
|
|
V2Authorizations: tc.AuthorizationIDs,
|
|
DnsNames: tc.OrderNames,
|
|
},
|
|
})
|
|
test.AssertNotError(t, err, "NewOrderAndAuthzs errored unexpectedly")
|
|
// If requested, set the order to processing
|
|
if tc.SetProcessing {
|
|
_, err := sa.SetOrderProcessing(ctx, &sapb.OrderRequest{Id: newOrder.Id})
|
|
test.AssertNotError(t, err, "Error setting order to processing status")
|
|
}
|
|
// If requested, finalize the order
|
|
if tc.Finalize {
|
|
newOrder.CertificateSerial = "lucky charms"
|
|
_, err = sa.FinalizeOrder(ctx, &sapb.FinalizeOrderRequest{Id: newOrder.Id, CertificateSerial: newOrder.CertificateSerial})
|
|
test.AssertNotError(t, err, "Error finalizing order")
|
|
}
|
|
// Fetch the order by ID to get its calculated status
|
|
storedOrder, err := sa.GetOrder(ctx, &sapb.OrderRequest{Id: newOrder.Id})
|
|
test.AssertNotError(t, err, "GetOrder failed")
|
|
// The status shouldn't be nil
|
|
test.AssertNotNil(t, storedOrder.Status, "Order status was nil")
|
|
// The status should match expected
|
|
test.AssertEquals(t, storedOrder.Status, tc.ExpectedStatus)
|
|
})
|
|
}
|
|
|
|
}
|
|
|
|
func TestUpdateChallengesDeleteUnused(t *testing.T) {
|
|
sa, fc, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
expires := fc.Now().Add(time.Hour)
|
|
ctx := context.Background()
|
|
attemptedAt := fc.Now()
|
|
|
|
// Create a valid authz
|
|
authzID := createFinalizedAuthorization(t, sa, "example.com", expires, "valid", attemptedAt)
|
|
|
|
result, err := sa.GetAuthorization2(ctx, &sapb.AuthorizationID2{Id: authzID})
|
|
test.AssertNotError(t, err, "sa.GetAuthorization2 failed")
|
|
|
|
if len(result.Challenges) != 1 {
|
|
t.Fatalf("expected 1 challenge left after finalization, got %d", len(result.Challenges))
|
|
}
|
|
if result.Challenges[0].Status != string(core.StatusValid) {
|
|
t.Errorf("expected challenge status %q, got %q", core.StatusValid, result.Challenges[0].Status)
|
|
}
|
|
if result.Challenges[0].Type != "http-01" {
|
|
t.Errorf("expected challenge type %q, got %q", "http-01", result.Challenges[0].Type)
|
|
}
|
|
}
|
|
|
|
func TestRevokeCertificate(t *testing.T) {
|
|
sa, fc, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
reg := createWorkingRegistration(t, sa)
|
|
// Add a cert to the DB to test with.
|
|
serial, testCert := test.ThrowAwayCert(t, fc)
|
|
issuedTime := sa.clk.Now()
|
|
_, err := sa.AddPrecertificate(ctx, &sapb.AddCertificateRequest{
|
|
Der: testCert.Raw,
|
|
RegID: reg.Id,
|
|
Issued: timestamppb.New(issuedTime),
|
|
IssuerNameID: 1,
|
|
})
|
|
test.AssertNotError(t, err, "Couldn't add test cert")
|
|
|
|
status, err := sa.GetCertificateStatus(ctx, &sapb.Serial{Serial: serial})
|
|
test.AssertNotError(t, err, "GetCertificateStatus failed")
|
|
test.AssertEquals(t, core.OCSPStatus(status.Status), core.OCSPStatusGood)
|
|
|
|
fc.Add(1 * time.Hour)
|
|
|
|
now := fc.Now()
|
|
reason := int64(1)
|
|
|
|
_, err = sa.RevokeCertificate(context.Background(), &sapb.RevokeCertificateRequest{
|
|
IssuerID: 1,
|
|
Serial: serial,
|
|
Date: timestamppb.New(now),
|
|
Reason: reason,
|
|
})
|
|
test.AssertNotError(t, err, "RevokeCertificate with no OCSP response should succeed")
|
|
|
|
status, err = sa.GetCertificateStatus(ctx, &sapb.Serial{Serial: serial})
|
|
test.AssertNotError(t, err, "GetCertificateStatus failed")
|
|
test.AssertEquals(t, core.OCSPStatus(status.Status), core.OCSPStatusRevoked)
|
|
test.AssertEquals(t, status.RevokedReason, reason)
|
|
test.AssertEquals(t, status.RevokedDate.AsTime(), now)
|
|
test.AssertEquals(t, status.OcspLastUpdated.AsTime(), now)
|
|
|
|
_, err = sa.RevokeCertificate(context.Background(), &sapb.RevokeCertificateRequest{
|
|
IssuerID: 1,
|
|
Serial: serial,
|
|
Date: timestamppb.New(now),
|
|
Reason: reason,
|
|
})
|
|
test.AssertError(t, err, "RevokeCertificate should've failed when certificate already revoked")
|
|
}
|
|
|
|
func TestRevokeCertificateWithShard(t *testing.T) {
|
|
if os.Getenv("BOULDER_CONFIG_DIR") != "test/config-next" {
|
|
t.Skip("Test requires revokedCertificates database table")
|
|
}
|
|
|
|
sa, fc, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
// Add a cert to the DB to test with.
|
|
reg := createWorkingRegistration(t, sa)
|
|
eeCert, err := core.LoadCert("../test/hierarchy/ee-e1.cert.pem")
|
|
test.AssertNotError(t, err, "failed to load test cert")
|
|
_, err = sa.AddSerial(ctx, &sapb.AddSerialRequest{
|
|
RegID: reg.Id,
|
|
Serial: core.SerialToString(eeCert.SerialNumber),
|
|
Created: timestamppb.New(eeCert.NotBefore),
|
|
Expires: timestamppb.New(eeCert.NotAfter),
|
|
})
|
|
test.AssertNotError(t, err, "failed to add test serial")
|
|
_, err = sa.AddPrecertificate(ctx, &sapb.AddCertificateRequest{
|
|
Der: eeCert.Raw,
|
|
RegID: reg.Id,
|
|
Issued: timestamppb.New(eeCert.NotBefore),
|
|
IssuerNameID: 1,
|
|
})
|
|
test.AssertNotError(t, err, "failed to add test cert")
|
|
|
|
serial := core.SerialToString(eeCert.SerialNumber)
|
|
fc.Add(1 * time.Hour)
|
|
now := fc.Now()
|
|
reason := int64(1)
|
|
|
|
_, err = sa.RevokeCertificate(context.Background(), &sapb.RevokeCertificateRequest{
|
|
IssuerID: 1,
|
|
ShardIdx: 9,
|
|
Serial: serial,
|
|
Date: timestamppb.New(now),
|
|
Reason: reason,
|
|
})
|
|
test.AssertNotError(t, err, "RevokeCertificate with no OCSP response should succeed")
|
|
|
|
status, err := sa.GetCertificateStatus(ctx, &sapb.Serial{Serial: serial})
|
|
test.AssertNotError(t, err, "GetCertificateStatus failed")
|
|
test.AssertEquals(t, core.OCSPStatus(status.Status), core.OCSPStatusRevoked)
|
|
test.AssertEquals(t, status.RevokedReason, reason)
|
|
test.AssertEquals(t, status.RevokedDate.AsTime(), now)
|
|
test.AssertEquals(t, status.OcspLastUpdated.AsTime(), now)
|
|
test.AssertEquals(t, status.NotAfter.AsTime(), eeCert.NotAfter)
|
|
|
|
var result revokedCertModel
|
|
err = sa.dbMap.SelectOne(
|
|
ctx, &result, `SELECT * FROM revokedCertificates WHERE serial = ?`, core.SerialToString(eeCert.SerialNumber))
|
|
test.AssertNotError(t, err, "should be exactly one row in revokedCertificates")
|
|
test.AssertEquals(t, result.ShardIdx, int64(9))
|
|
test.AssertEquals(t, result.RevokedReason, revocation.Reason(ocsp.KeyCompromise))
|
|
}
|
|
|
|
func TestUpdateRevokedCertificate(t *testing.T) {
|
|
sa, fc, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
// Add a cert to the DB to test with.
|
|
reg := createWorkingRegistration(t, sa)
|
|
serial, testCert := test.ThrowAwayCert(t, fc)
|
|
issuedTime := fc.Now()
|
|
_, err := sa.AddPrecertificate(ctx, &sapb.AddCertificateRequest{
|
|
Der: testCert.Raw,
|
|
RegID: reg.Id,
|
|
Issued: timestamppb.New(issuedTime),
|
|
IssuerNameID: 1,
|
|
})
|
|
test.AssertNotError(t, err, "Couldn't add test cert")
|
|
fc.Add(1 * time.Hour)
|
|
|
|
// Try to update it before its been revoked
|
|
now := fc.Now()
|
|
_, err = sa.UpdateRevokedCertificate(context.Background(), &sapb.RevokeCertificateRequest{
|
|
IssuerID: 1,
|
|
Serial: serial,
|
|
Date: timestamppb.New(now),
|
|
Backdate: timestamppb.New(now),
|
|
Reason: ocsp.KeyCompromise,
|
|
Response: []byte{4, 5, 6},
|
|
})
|
|
test.AssertError(t, err, "UpdateRevokedCertificate should have failed")
|
|
test.AssertContains(t, err.Error(), "no certificate with serial")
|
|
|
|
// Now revoke it, so we can update it.
|
|
revokedTime := fc.Now()
|
|
_, err = sa.RevokeCertificate(context.Background(), &sapb.RevokeCertificateRequest{
|
|
IssuerID: 1,
|
|
Serial: serial,
|
|
Date: timestamppb.New(revokedTime),
|
|
Reason: ocsp.CessationOfOperation,
|
|
Response: []byte{1, 2, 3},
|
|
})
|
|
test.AssertNotError(t, err, "RevokeCertificate failed")
|
|
|
|
// Double check that setup worked.
|
|
status, err := sa.GetCertificateStatus(ctx, &sapb.Serial{Serial: serial})
|
|
test.AssertNotError(t, err, "GetCertificateStatus failed")
|
|
test.AssertEquals(t, core.OCSPStatus(status.Status), core.OCSPStatusRevoked)
|
|
test.AssertEquals(t, int(status.RevokedReason), ocsp.CessationOfOperation)
|
|
fc.Add(1 * time.Hour)
|
|
|
|
// Try to update its revocation info with no backdate
|
|
now = fc.Now()
|
|
_, err = sa.UpdateRevokedCertificate(context.Background(), &sapb.RevokeCertificateRequest{
|
|
IssuerID: 1,
|
|
Serial: serial,
|
|
Date: timestamppb.New(now),
|
|
Reason: ocsp.KeyCompromise,
|
|
Response: []byte{4, 5, 6},
|
|
})
|
|
test.AssertError(t, err, "UpdateRevokedCertificate should have failed")
|
|
test.AssertContains(t, err.Error(), "incomplete")
|
|
|
|
// Try to update its revocation info for a reason other than keyCompromise
|
|
_, err = sa.UpdateRevokedCertificate(context.Background(), &sapb.RevokeCertificateRequest{
|
|
IssuerID: 1,
|
|
Serial: serial,
|
|
Date: timestamppb.New(now),
|
|
Backdate: timestamppb.New(revokedTime),
|
|
Reason: ocsp.Unspecified,
|
|
Response: []byte{4, 5, 6},
|
|
})
|
|
test.AssertError(t, err, "UpdateRevokedCertificate should have failed")
|
|
test.AssertContains(t, err.Error(), "cannot update revocation for any reason other than keyCompromise")
|
|
|
|
// Try to update the revocation info of the wrong certificate
|
|
_, err = sa.UpdateRevokedCertificate(context.Background(), &sapb.RevokeCertificateRequest{
|
|
IssuerID: 1,
|
|
Serial: "000000000000000000000000000000021bd5",
|
|
Date: timestamppb.New(now),
|
|
Backdate: timestamppb.New(revokedTime),
|
|
Reason: ocsp.KeyCompromise,
|
|
Response: []byte{4, 5, 6},
|
|
})
|
|
test.AssertError(t, err, "UpdateRevokedCertificate should have failed")
|
|
test.AssertContains(t, err.Error(), "no certificate with serial")
|
|
|
|
// Try to update its revocation info with the wrong backdate
|
|
_, err = sa.UpdateRevokedCertificate(context.Background(), &sapb.RevokeCertificateRequest{
|
|
IssuerID: 1,
|
|
Serial: serial,
|
|
Date: timestamppb.New(now),
|
|
Backdate: timestamppb.New(now),
|
|
Reason: ocsp.KeyCompromise,
|
|
Response: []byte{4, 5, 6},
|
|
})
|
|
test.AssertError(t, err, "UpdateRevokedCertificate should have failed")
|
|
test.AssertContains(t, err.Error(), "no certificate with serial")
|
|
|
|
// Try to update its revocation info correctly
|
|
_, err = sa.UpdateRevokedCertificate(context.Background(), &sapb.RevokeCertificateRequest{
|
|
IssuerID: 1,
|
|
Serial: serial,
|
|
Date: timestamppb.New(now),
|
|
Backdate: timestamppb.New(revokedTime),
|
|
Reason: ocsp.KeyCompromise,
|
|
Response: []byte{4, 5, 6},
|
|
})
|
|
test.AssertNotError(t, err, "UpdateRevokedCertificate failed")
|
|
}
|
|
|
|
func TestUpdateRevokedCertificateWithShard(t *testing.T) {
|
|
if os.Getenv("BOULDER_CONFIG_DIR") != "test/config-next" {
|
|
t.Skip("Test requires revokedCertificates database table")
|
|
}
|
|
|
|
sa, fc, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
// Add a cert to the DB to test with.
|
|
reg := createWorkingRegistration(t, sa)
|
|
serial, testCert := test.ThrowAwayCert(t, fc)
|
|
_, err := sa.AddSerial(ctx, &sapb.AddSerialRequest{
|
|
RegID: reg.Id,
|
|
Serial: core.SerialToString(testCert.SerialNumber),
|
|
Created: timestamppb.New(testCert.NotBefore),
|
|
Expires: timestamppb.New(testCert.NotAfter),
|
|
})
|
|
test.AssertNotError(t, err, "failed to add test serial")
|
|
_, err = sa.AddPrecertificate(ctx, &sapb.AddCertificateRequest{
|
|
Der: testCert.Raw,
|
|
RegID: reg.Id,
|
|
Issued: timestamppb.New(testCert.NotBefore),
|
|
IssuerNameID: 1,
|
|
})
|
|
test.AssertNotError(t, err, "Couldn't add test cert")
|
|
fc.Add(1 * time.Hour)
|
|
|
|
// Now revoke it with a shardIdx, so that it gets updated in both the
|
|
// certificateStatus table and the revokedCertificates table.
|
|
revokedTime := fc.Now()
|
|
_, err = sa.RevokeCertificate(context.Background(), &sapb.RevokeCertificateRequest{
|
|
IssuerID: 1,
|
|
ShardIdx: 9,
|
|
Serial: serial,
|
|
Date: timestamppb.New(revokedTime),
|
|
Reason: ocsp.CessationOfOperation,
|
|
Response: []byte{1, 2, 3},
|
|
})
|
|
test.AssertNotError(t, err, "RevokeCertificate failed")
|
|
|
|
// Updating revocation should succeed, with the revokedCertificates row being
|
|
// updated.
|
|
_, err = sa.UpdateRevokedCertificate(context.Background(), &sapb.RevokeCertificateRequest{
|
|
IssuerID: 1,
|
|
ShardIdx: 9,
|
|
Serial: serial,
|
|
Date: timestamppb.New(fc.Now()),
|
|
Backdate: timestamppb.New(revokedTime),
|
|
Reason: ocsp.KeyCompromise,
|
|
Response: []byte{4, 5, 6},
|
|
})
|
|
test.AssertNotError(t, err, "UpdateRevokedCertificate failed")
|
|
|
|
var result revokedCertModel
|
|
err = sa.dbMap.SelectOne(
|
|
ctx, &result, `SELECT * FROM revokedCertificates WHERE serial = ?`, serial)
|
|
test.AssertNotError(t, err, "should be exactly one row in revokedCertificates")
|
|
test.AssertEquals(t, result.ShardIdx, int64(9))
|
|
test.AssertEquals(t, result.RevokedReason, revocation.Reason(ocsp.KeyCompromise))
|
|
}
|
|
|
|
func TestUpdateRevokedCertificateWithShardInterim(t *testing.T) {
|
|
if os.Getenv("BOULDER_CONFIG_DIR") != "test/config-next" {
|
|
t.Skip("Test requires revokedCertificates database table")
|
|
}
|
|
|
|
sa, fc, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
// Add a cert to the DB to test with.
|
|
reg := createWorkingRegistration(t, sa)
|
|
serial, testCert := test.ThrowAwayCert(t, fc)
|
|
_, err := sa.AddSerial(ctx, &sapb.AddSerialRequest{
|
|
RegID: reg.Id,
|
|
Serial: serial,
|
|
Created: timestamppb.New(testCert.NotBefore),
|
|
Expires: timestamppb.New(testCert.NotAfter),
|
|
})
|
|
test.AssertNotError(t, err, "failed to add test serial")
|
|
_, err = sa.AddPrecertificate(ctx, &sapb.AddCertificateRequest{
|
|
Der: testCert.Raw,
|
|
RegID: reg.Id,
|
|
Issued: timestamppb.New(testCert.NotBefore),
|
|
IssuerNameID: 1,
|
|
})
|
|
test.AssertNotError(t, err, "Couldn't add test cert")
|
|
fc.Add(1 * time.Hour)
|
|
|
|
// Now revoke it *without* a shardIdx, so that it only gets updated in the
|
|
// certificateStatus table, and not the revokedCertificates table.
|
|
revokedTime := timestamppb.New(fc.Now())
|
|
_, err = sa.RevokeCertificate(context.Background(), &sapb.RevokeCertificateRequest{
|
|
IssuerID: 1,
|
|
Serial: serial,
|
|
Date: revokedTime,
|
|
Reason: ocsp.CessationOfOperation,
|
|
Response: []byte{1, 2, 3},
|
|
})
|
|
test.AssertNotError(t, err, "RevokeCertificate failed")
|
|
|
|
// Confirm that setup worked as expected.
|
|
status, err := sa.GetCertificateStatus(
|
|
ctx, &sapb.Serial{Serial: serial})
|
|
test.AssertNotError(t, err, "GetCertificateStatus failed")
|
|
test.AssertEquals(t, core.OCSPStatus(status.Status), core.OCSPStatusRevoked)
|
|
|
|
c, err := sa.dbMap.SelectNullInt(
|
|
ctx, "SELECT count(*) FROM revokedCertificates")
|
|
test.AssertNotError(t, err, "SELECT from revokedCertificates failed")
|
|
test.Assert(t, c.Valid, "SELECT from revokedCertificates got no result")
|
|
test.AssertEquals(t, c.Int64, int64(0))
|
|
|
|
// Updating revocation should succeed, with a new row being written into the
|
|
// revokedCertificates table.
|
|
_, err = sa.UpdateRevokedCertificate(context.Background(), &sapb.RevokeCertificateRequest{
|
|
IssuerID: 1,
|
|
ShardIdx: 9,
|
|
Serial: serial,
|
|
Date: timestamppb.New(fc.Now()),
|
|
Backdate: revokedTime,
|
|
Reason: ocsp.KeyCompromise,
|
|
Response: []byte{4, 5, 6},
|
|
})
|
|
test.AssertNotError(t, err, "UpdateRevokedCertificate failed")
|
|
|
|
var result revokedCertModel
|
|
err = sa.dbMap.SelectOne(
|
|
ctx, &result, `SELECT * FROM revokedCertificates WHERE serial = ?`, serial)
|
|
test.AssertNotError(t, err, "should be exactly one row in revokedCertificates")
|
|
test.AssertEquals(t, result.ShardIdx, int64(9))
|
|
test.AssertEquals(t, result.RevokedReason, revocation.Reason(ocsp.KeyCompromise))
|
|
}
|
|
|
|
func TestAddCertificateRenewalBit(t *testing.T) {
|
|
sa, fc, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
reg := createWorkingRegistration(t, sa)
|
|
|
|
assertIsRenewal := func(t *testing.T, name string, expected bool) {
|
|
t.Helper()
|
|
var count int
|
|
err := sa.dbMap.SelectOne(
|
|
ctx,
|
|
&count,
|
|
`SELECT COUNT(*) FROM issuedNames
|
|
WHERE reversedName = ?
|
|
AND renewal = ?`,
|
|
ReverseName(name),
|
|
expected,
|
|
)
|
|
test.AssertNotError(t, err, "Unexpected error from SelectOne on issuedNames")
|
|
test.AssertEquals(t, count, 1)
|
|
}
|
|
|
|
// Add a certificate with a never-before-seen name.
|
|
_, testCert := test.ThrowAwayCert(t, fc)
|
|
_, err := sa.AddPrecertificate(ctx, &sapb.AddCertificateRequest{
|
|
Der: testCert.Raw,
|
|
Issued: timestamppb.New(testCert.NotBefore),
|
|
RegID: reg.Id,
|
|
IssuerNameID: 1,
|
|
})
|
|
test.AssertNotError(t, err, "Failed to add precertificate")
|
|
_, err = sa.AddCertificate(ctx, &sapb.AddCertificateRequest{
|
|
Der: testCert.Raw,
|
|
RegID: reg.Id,
|
|
Issued: timestamppb.New(testCert.NotBefore),
|
|
})
|
|
test.AssertNotError(t, err, "Failed to add certificate")
|
|
|
|
// None of the names should have a issuedNames row marking it as a renewal.
|
|
for _, name := range testCert.DNSNames {
|
|
assertIsRenewal(t, name, false)
|
|
}
|
|
|
|
// Make a new cert and add its FQDN set to the db so it will be considered a
|
|
// renewal
|
|
serial, testCert := test.ThrowAwayCert(t, fc)
|
|
err = addFQDNSet(ctx, sa.dbMap, testCert.DNSNames, serial, testCert.NotBefore, testCert.NotAfter)
|
|
test.AssertNotError(t, err, "Failed to add name set")
|
|
_, err = sa.AddPrecertificate(ctx, &sapb.AddCertificateRequest{
|
|
Der: testCert.Raw,
|
|
Issued: timestamppb.New(testCert.NotBefore),
|
|
RegID: reg.Id,
|
|
IssuerNameID: 1,
|
|
})
|
|
test.AssertNotError(t, err, "Failed to add precertificate")
|
|
_, err = sa.AddCertificate(ctx, &sapb.AddCertificateRequest{
|
|
Der: testCert.Raw,
|
|
RegID: reg.Id,
|
|
Issued: timestamppb.New(testCert.NotBefore),
|
|
})
|
|
test.AssertNotError(t, err, "Failed to add certificate")
|
|
|
|
// All of the names should have a issuedNames row marking it as a renewal.
|
|
for _, name := range testCert.DNSNames {
|
|
assertIsRenewal(t, name, true)
|
|
}
|
|
}
|
|
|
|
func TestCountCertificatesRenewalBit(t *testing.T) {
|
|
sa, fc, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
// Create a test registration
|
|
reg := createWorkingRegistration(t, sa)
|
|
|
|
// Create a small throw away key for the test certificates.
|
|
testKey, err := rsa.GenerateKey(rand.Reader, 512)
|
|
test.AssertNotError(t, err, "error generating test key")
|
|
|
|
// Create an initial test certificate for a set of domain names, issued an
|
|
// hour ago.
|
|
template := &x509.Certificate{
|
|
SerialNumber: big.NewInt(1337),
|
|
DNSNames: []string{"www.not-example.com", "not-example.com", "admin.not-example.com"},
|
|
NotBefore: fc.Now().Add(-time.Hour),
|
|
BasicConstraintsValid: true,
|
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
|
|
}
|
|
certADER, err := x509.CreateCertificate(rand.Reader, template, template, testKey.Public(), testKey)
|
|
test.AssertNotError(t, err, "Failed to create test cert A")
|
|
certA, _ := x509.ParseCertificate(certADER)
|
|
|
|
// Update the template with a new serial number and a not before of now and
|
|
// create a second test cert for the same names. This will be a renewal.
|
|
template.SerialNumber = big.NewInt(7331)
|
|
template.NotBefore = fc.Now()
|
|
certBDER, err := x509.CreateCertificate(rand.Reader, template, template, testKey.Public(), testKey)
|
|
test.AssertNotError(t, err, "Failed to create test cert B")
|
|
certB, _ := x509.ParseCertificate(certBDER)
|
|
|
|
// Update the template with a third serial number and a partially overlapping
|
|
// set of names. This will not be a renewal but will help test the exact name
|
|
// counts.
|
|
template.SerialNumber = big.NewInt(0xC0FFEE)
|
|
template.DNSNames = []string{"www.not-example.com"}
|
|
certCDER, err := x509.CreateCertificate(rand.Reader, template, template, testKey.Public(), testKey)
|
|
test.AssertNotError(t, err, "Failed to create test cert C")
|
|
|
|
countName := func(t *testing.T, expectedName string) int64 {
|
|
earliest := fc.Now().Add(-5 * time.Hour)
|
|
latest := fc.Now().Add(5 * time.Hour)
|
|
req := &sapb.CountCertificatesByNamesRequest{
|
|
DnsNames: []string{expectedName},
|
|
Range: &sapb.Range{
|
|
Earliest: timestamppb.New(earliest),
|
|
Latest: timestamppb.New(latest),
|
|
},
|
|
}
|
|
counts, err := sa.CountCertificatesByNames(context.Background(), req)
|
|
test.AssertNotError(t, err, "Unexpected err from CountCertificatesByNames")
|
|
for name, count := range counts.Counts {
|
|
if name == expectedName {
|
|
return count
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// Add the first certificate - it won't be considered a renewal.
|
|
issued := certA.NotBefore
|
|
_, err = sa.AddCertificate(ctx, &sapb.AddCertificateRequest{
|
|
Der: certADER,
|
|
RegID: reg.Id,
|
|
Issued: timestamppb.New(issued),
|
|
})
|
|
test.AssertNotError(t, err, "Failed to add CertA test certificate")
|
|
|
|
// The count for the base domain should be 1 - just certA has been added.
|
|
test.AssertEquals(t, countName(t, "not-example.com"), int64(1))
|
|
|
|
// Add the second certificate - it should be considered a renewal
|
|
issued = certB.NotBefore
|
|
_, err = sa.AddCertificate(ctx, &sapb.AddCertificateRequest{
|
|
Der: certBDER,
|
|
RegID: reg.Id,
|
|
Issued: timestamppb.New(issued),
|
|
})
|
|
test.AssertNotError(t, err, "Failed to add CertB test certificate")
|
|
|
|
// The count for the base domain should still be 1, just certA. CertB should
|
|
// be ignored.
|
|
test.AssertEquals(t, countName(t, "not-example.com"), int64(1))
|
|
|
|
// Add the third certificate - it should not be considered a renewal
|
|
_, err = sa.AddCertificate(ctx, &sapb.AddCertificateRequest{
|
|
Der: certCDER,
|
|
RegID: reg.Id,
|
|
Issued: timestamppb.New(issued),
|
|
})
|
|
test.AssertNotError(t, err, "Failed to add CertC test certificate")
|
|
|
|
// The count for the base domain should be 2 now: certA and certC.
|
|
// CertB should be ignored.
|
|
test.AssertEquals(t, countName(t, "not-example.com"), int64(2))
|
|
}
|
|
|
|
func TestFinalizeAuthorization2(t *testing.T) {
|
|
sa, fc, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
fc.Set(time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC))
|
|
|
|
authzID := createPendingAuthorization(t, sa, "aaa", fc.Now().Add(time.Hour))
|
|
expires := fc.Now().Add(time.Hour * 2).UTC()
|
|
attemptedAt := fc.Now()
|
|
ip, _ := net.ParseIP("1.1.1.1").MarshalText()
|
|
|
|
_, err := sa.FinalizeAuthorization2(context.Background(), &sapb.FinalizeAuthorizationRequest{
|
|
Id: authzID,
|
|
ValidationRecords: []*corepb.ValidationRecord{
|
|
{
|
|
Hostname: "example.com",
|
|
Port: "80",
|
|
Url: "http://example.com",
|
|
AddressUsed: ip,
|
|
ResolverAddrs: []string{"resolver:5353"},
|
|
},
|
|
},
|
|
Status: string(core.StatusValid),
|
|
Expires: timestamppb.New(expires),
|
|
Attempted: string(core.ChallengeTypeHTTP01),
|
|
AttemptedAt: timestamppb.New(attemptedAt),
|
|
})
|
|
test.AssertNotError(t, err, "sa.FinalizeAuthorization2 failed")
|
|
|
|
dbVer, err := sa.GetAuthorization2(context.Background(), &sapb.AuthorizationID2{Id: authzID})
|
|
test.AssertNotError(t, err, "sa.GetAuthorization2 failed")
|
|
test.AssertEquals(t, dbVer.Status, string(core.StatusValid))
|
|
test.AssertEquals(t, dbVer.Expires.AsTime(), expires)
|
|
test.AssertEquals(t, dbVer.Challenges[0].Status, string(core.StatusValid))
|
|
test.AssertEquals(t, len(dbVer.Challenges[0].Validationrecords), 1)
|
|
test.AssertEquals(t, dbVer.Challenges[0].Validationrecords[0].Hostname, "example.com")
|
|
test.AssertEquals(t, dbVer.Challenges[0].Validationrecords[0].Port, "80")
|
|
test.AssertEquals(t, dbVer.Challenges[0].Validationrecords[0].ResolverAddrs[0], "resolver:5353")
|
|
test.AssertEquals(t, dbVer.Challenges[0].Validated.AsTime(), attemptedAt)
|
|
|
|
authzID = createPendingAuthorization(t, sa, "aaa", fc.Now().Add(time.Hour))
|
|
prob, _ := bgrpc.ProblemDetailsToPB(probs.Connection("it went bad captain"))
|
|
|
|
_, err = sa.FinalizeAuthorization2(context.Background(), &sapb.FinalizeAuthorizationRequest{
|
|
Id: authzID,
|
|
ValidationRecords: []*corepb.ValidationRecord{
|
|
{
|
|
Hostname: "example.com",
|
|
Port: "80",
|
|
Url: "http://example.com",
|
|
AddressUsed: ip,
|
|
ResolverAddrs: []string{"resolver:5353"},
|
|
},
|
|
},
|
|
ValidationError: prob,
|
|
Status: string(core.StatusInvalid),
|
|
Attempted: string(core.ChallengeTypeHTTP01),
|
|
Expires: timestamppb.New(expires),
|
|
})
|
|
test.AssertNotError(t, err, "sa.FinalizeAuthorization2 failed")
|
|
|
|
dbVer, err = sa.GetAuthorization2(context.Background(), &sapb.AuthorizationID2{Id: authzID})
|
|
test.AssertNotError(t, err, "sa.GetAuthorization2 failed")
|
|
test.AssertEquals(t, dbVer.Status, string(core.StatusInvalid))
|
|
test.AssertEquals(t, dbVer.Challenges[0].Status, string(core.StatusInvalid))
|
|
test.AssertEquals(t, len(dbVer.Challenges[0].Validationrecords), 1)
|
|
test.AssertEquals(t, dbVer.Challenges[0].Validationrecords[0].Hostname, "example.com")
|
|
test.AssertEquals(t, dbVer.Challenges[0].Validationrecords[0].Port, "80")
|
|
test.AssertEquals(t, dbVer.Challenges[0].Validationrecords[0].ResolverAddrs[0], "resolver:5353")
|
|
test.AssertDeepEquals(t, dbVer.Challenges[0].Error, prob)
|
|
}
|
|
|
|
func TestRehydrateHostPort(t *testing.T) {
|
|
sa, fc, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
fc.Set(time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC))
|
|
|
|
expires := fc.Now().Add(time.Hour * 2).UTC()
|
|
attemptedAt := fc.Now()
|
|
ip, _ := net.ParseIP("1.1.1.1").MarshalText()
|
|
|
|
// Implicit good port with good scheme
|
|
authzID := createPendingAuthorization(t, sa, "aaa", fc.Now().Add(time.Hour))
|
|
_, err := sa.FinalizeAuthorization2(context.Background(), &sapb.FinalizeAuthorizationRequest{
|
|
Id: authzID,
|
|
ValidationRecords: []*corepb.ValidationRecord{
|
|
{
|
|
Hostname: "example.com",
|
|
Port: "80",
|
|
Url: "http://example.com",
|
|
AddressUsed: ip,
|
|
},
|
|
},
|
|
Status: string(core.StatusValid),
|
|
Expires: timestamppb.New(expires),
|
|
Attempted: string(core.ChallengeTypeHTTP01),
|
|
AttemptedAt: timestamppb.New(attemptedAt),
|
|
})
|
|
test.AssertNotError(t, err, "sa.FinalizeAuthorization2 failed")
|
|
_, err = sa.GetAuthorization2(context.Background(), &sapb.AuthorizationID2{Id: authzID})
|
|
test.AssertNotError(t, err, "rehydration failed in some fun and interesting way")
|
|
|
|
// Explicit good port with good scheme
|
|
authzID = createPendingAuthorization(t, sa, "aaa", fc.Now().Add(time.Hour))
|
|
_, err = sa.FinalizeAuthorization2(context.Background(), &sapb.FinalizeAuthorizationRequest{
|
|
Id: authzID,
|
|
ValidationRecords: []*corepb.ValidationRecord{
|
|
{
|
|
Hostname: "example.com",
|
|
Port: "80",
|
|
Url: "http://example.com:80",
|
|
AddressUsed: ip,
|
|
},
|
|
},
|
|
Status: string(core.StatusValid),
|
|
Expires: timestamppb.New(expires),
|
|
Attempted: string(core.ChallengeTypeHTTP01),
|
|
AttemptedAt: timestamppb.New(attemptedAt),
|
|
})
|
|
test.AssertNotError(t, err, "sa.FinalizeAuthorization2 failed")
|
|
_, err = sa.GetAuthorization2(context.Background(), &sapb.AuthorizationID2{Id: authzID})
|
|
test.AssertNotError(t, err, "rehydration failed in some fun and interesting way")
|
|
|
|
// Explicit bad port with good scheme
|
|
authzID = createPendingAuthorization(t, sa, "aaa", fc.Now().Add(time.Hour))
|
|
_, err = sa.FinalizeAuthorization2(context.Background(), &sapb.FinalizeAuthorizationRequest{
|
|
Id: authzID,
|
|
ValidationRecords: []*corepb.ValidationRecord{
|
|
{
|
|
Hostname: "example.com",
|
|
Port: "444",
|
|
Url: "http://example.com:444",
|
|
AddressUsed: ip,
|
|
},
|
|
},
|
|
Status: string(core.StatusValid),
|
|
Expires: timestamppb.New(expires),
|
|
Attempted: string(core.ChallengeTypeHTTP01),
|
|
AttemptedAt: timestamppb.New(attemptedAt),
|
|
})
|
|
test.AssertNotError(t, err, "sa.FinalizeAuthorization2 failed")
|
|
_, err = sa.GetAuthorization2(context.Background(), &sapb.AuthorizationID2{Id: authzID})
|
|
test.AssertError(t, err, "only ports 80/tcp and 443/tcp are allowed in URL \"http://example.com:444\"")
|
|
|
|
// Explicit bad port with bad scheme
|
|
authzID = createPendingAuthorization(t, sa, "aaa", fc.Now().Add(time.Hour))
|
|
_, err = sa.FinalizeAuthorization2(context.Background(), &sapb.FinalizeAuthorizationRequest{
|
|
Id: authzID,
|
|
ValidationRecords: []*corepb.ValidationRecord{
|
|
{
|
|
Hostname: "example.com",
|
|
Port: "80",
|
|
Url: "httpx://example.com",
|
|
AddressUsed: ip,
|
|
},
|
|
},
|
|
Status: string(core.StatusValid),
|
|
Expires: timestamppb.New(expires),
|
|
Attempted: string(core.ChallengeTypeHTTP01),
|
|
AttemptedAt: timestamppb.New(attemptedAt),
|
|
})
|
|
test.AssertNotError(t, err, "sa.FinalizeAuthorization2 failed")
|
|
_, err = sa.GetAuthorization2(context.Background(), &sapb.AuthorizationID2{Id: authzID})
|
|
test.AssertError(t, err, "unknown scheme \"httpx\" in URL \"httpx://example.com\"")
|
|
|
|
// Missing URL field
|
|
authzID = createPendingAuthorization(t, sa, "aaa", fc.Now().Add(time.Hour))
|
|
_, err = sa.FinalizeAuthorization2(context.Background(), &sapb.FinalizeAuthorizationRequest{
|
|
Id: authzID,
|
|
ValidationRecords: []*corepb.ValidationRecord{
|
|
{
|
|
Hostname: "example.com",
|
|
Port: "80",
|
|
AddressUsed: ip,
|
|
},
|
|
},
|
|
Status: string(core.StatusValid),
|
|
Expires: timestamppb.New(expires),
|
|
Attempted: string(core.ChallengeTypeHTTP01),
|
|
AttemptedAt: timestamppb.New(attemptedAt),
|
|
})
|
|
test.AssertNotError(t, err, "sa.FinalizeAuthorization2 failed")
|
|
_, err = sa.GetAuthorization2(context.Background(), &sapb.AuthorizationID2{Id: authzID})
|
|
test.AssertError(t, err, "URL field cannot be empty")
|
|
}
|
|
|
|
func TestCountPendingAuthorizations2(t *testing.T) {
|
|
sa, fc, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
expiresA := fc.Now().Add(time.Hour).UTC()
|
|
expiresB := fc.Now().Add(time.Hour * 3).UTC()
|
|
_ = createPendingAuthorization(t, sa, "example.com", expiresA)
|
|
_ = createPendingAuthorization(t, sa, "example.com", expiresB)
|
|
|
|
// Registration has two new style pending authorizations
|
|
regID := int64(1)
|
|
count, err := sa.CountPendingAuthorizations2(context.Background(), &sapb.RegistrationID{
|
|
Id: regID,
|
|
})
|
|
test.AssertNotError(t, err, "sa.CountPendingAuthorizations2 failed")
|
|
test.AssertEquals(t, count.Count, int64(2))
|
|
|
|
// Registration has two new style pending authorizations, one of which has expired
|
|
fc.Add(time.Hour * 2)
|
|
count, err = sa.CountPendingAuthorizations2(context.Background(), &sapb.RegistrationID{
|
|
Id: regID,
|
|
})
|
|
test.AssertNotError(t, err, "sa.CountPendingAuthorizations2 failed")
|
|
test.AssertEquals(t, count.Count, int64(1))
|
|
|
|
// Registration with no authorizations should be 0
|
|
noReg := int64(20)
|
|
count, err = sa.CountPendingAuthorizations2(context.Background(), &sapb.RegistrationID{
|
|
Id: noReg,
|
|
})
|
|
test.AssertNotError(t, err, "sa.CountPendingAuthorizations2 failed")
|
|
test.AssertEquals(t, count.Count, int64(0))
|
|
}
|
|
|
|
func TestAuthzModelMapToPB(t *testing.T) {
|
|
baseExpires := time.Now()
|
|
input := map[string]authzModel{
|
|
"example.com": {
|
|
ID: 123,
|
|
IdentifierType: 0,
|
|
IdentifierValue: "example.com",
|
|
RegistrationID: 77,
|
|
Status: 1,
|
|
Expires: baseExpires,
|
|
Challenges: 4,
|
|
},
|
|
"www.example.com": {
|
|
ID: 124,
|
|
IdentifierType: 0,
|
|
IdentifierValue: "www.example.com",
|
|
RegistrationID: 77,
|
|
Status: 1,
|
|
Expires: baseExpires,
|
|
Challenges: 1,
|
|
},
|
|
"other.example.net": {
|
|
ID: 125,
|
|
IdentifierType: 0,
|
|
IdentifierValue: "other.example.net",
|
|
RegistrationID: 77,
|
|
Status: 1,
|
|
Expires: baseExpires,
|
|
Challenges: 3,
|
|
},
|
|
}
|
|
|
|
out, err := authzModelMapToPB(input)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
for _, authzPB := range out.Authzs {
|
|
model, ok := input[authzPB.DnsName]
|
|
if !ok {
|
|
t.Errorf("output had element for %q, a hostname not present in input", authzPB.DnsName)
|
|
}
|
|
test.AssertEquals(t, authzPB.Id, fmt.Sprintf("%d", model.ID))
|
|
test.AssertEquals(t, authzPB.DnsName, model.IdentifierValue)
|
|
test.AssertEquals(t, authzPB.RegistrationID, model.RegistrationID)
|
|
test.AssertEquals(t, authzPB.Status, string(uintToStatus[model.Status]))
|
|
gotTime := authzPB.Expires.AsTime()
|
|
if !model.Expires.Equal(gotTime) {
|
|
t.Errorf("Times didn't match. Got %s, expected %s (%s)", gotTime, model.Expires, authzPB.Expires.AsTime())
|
|
}
|
|
if len(authzPB.Challenges) != bits.OnesCount(uint(model.Challenges)) {
|
|
t.Errorf("wrong number of challenges for %q: got %d, expected %d", authzPB.DnsName,
|
|
len(authzPB.Challenges), bits.OnesCount(uint(model.Challenges)))
|
|
}
|
|
switch model.Challenges {
|
|
case 1:
|
|
test.AssertEquals(t, authzPB.Challenges[0].Type, "http-01")
|
|
case 3:
|
|
test.AssertEquals(t, authzPB.Challenges[0].Type, "http-01")
|
|
test.AssertEquals(t, authzPB.Challenges[1].Type, "dns-01")
|
|
case 4:
|
|
test.AssertEquals(t, authzPB.Challenges[0].Type, "tls-alpn-01")
|
|
}
|
|
|
|
delete(input, authzPB.DnsName)
|
|
}
|
|
|
|
for k := range input {
|
|
t.Errorf("hostname %q was not present in output", k)
|
|
}
|
|
}
|
|
|
|
func TestGetValidOrderAuthorizations2(t *testing.T) {
|
|
sa, fc, cleanup := initSA(t)
|
|
defer cleanup()
|
|
|
|
// Create two new valid authorizations
|
|
reg := createWorkingRegistration(t, sa)
|
|
identA := "a.example.com"
|
|
identB := "b.example.com"
|
|
expires := fc.Now().Add(time.Hour * 24 * 7).UTC()
|
|
attemptedAt := fc.Now()
|
|
|
|
authzIDA := createFinalizedAuthorization(t, sa, identA, expires, "valid", attemptedAt)
|
|
authzIDB := createFinalizedAuthorization(t, sa, identB, expires, "valid", attemptedAt)
|
|
|
|
orderExpr := fc.Now().Truncate(time.Second)
|
|
order, err := sa.NewOrderAndAuthzs(context.Background(), &sapb.NewOrderAndAuthzsRequest{
|
|
NewOrder: &sapb.NewOrderRequest{
|
|
RegistrationID: reg.Id,
|
|
Expires: timestamppb.New(orderExpr),
|
|
DnsNames: []string{"a.example.com", "b.example.com"},
|
|
V2Authorizations: []int64{authzIDA, authzIDB},
|
|
},
|
|
})
|
|
test.AssertNotError(t, err, "AddOrder failed")
|
|
|
|
authzPBs, err := sa.GetValidOrderAuthorizations2(
|
|
context.Background(),
|
|
&sapb.GetValidOrderAuthorizationsRequest{
|
|
Id: order.Id,
|
|
AcctID: reg.Id,
|
|
})
|
|
test.AssertNotError(t, err, "sa.GetValidOrderAuthorizations failed")
|
|
test.AssertNotNil(t, authzPBs, "sa.GetValidOrderAuthorizations result was nil")
|
|
test.AssertEquals(t, len(authzPBs.Authzs), 2)
|
|
|
|
namesToCheck := map[string]int64{"a.example.com": authzIDA, "b.example.com": authzIDB}
|
|
for _, a := range authzPBs.Authzs {
|
|
if fmt.Sprintf("%d", namesToCheck[a.DnsName]) != a.Id {
|
|
t.Fatalf("incorrect identifier %q with id %s", a.DnsName, a.Id)
|
|
}
|
|
test.AssertEquals(t, a.Expires.AsTime(), expires)
|
|
delete(namesToCheck, a.DnsName)
|
|
}
|
|
|
|
// Getting the order authorizations for an order that doesn't exist should return nothing
|
|
missingID := int64(0xC0FFEEEEEEE)
|
|
authzPBs, err = sa.GetValidOrderAuthorizations2(
|
|
context.Background(),
|
|
&sapb.GetValidOrderAuthorizationsRequest{
|
|
Id: missingID,
|
|
AcctID: reg.Id,
|
|
})
|
|
test.AssertNotError(t, err, "sa.GetValidOrderAuthorizations failed")
|
|
test.AssertEquals(t, len(authzPBs.Authzs), 0)
|
|
}
|
|
|
|
func TestCountInvalidAuthorizations2(t *testing.T) {
|
|
sa, fc, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
// Create two authorizations, one pending, one invalid
|
|
fc.Add(time.Hour)
|
|
reg := createWorkingRegistration(t, sa)
|
|
ident := "aaa"
|
|
expiresA := fc.Now().Add(time.Hour).UTC()
|
|
expiresB := fc.Now().Add(time.Hour * 3).UTC()
|
|
attemptedAt := fc.Now()
|
|
_ = createFinalizedAuthorization(t, sa, ident, expiresA, "invalid", attemptedAt)
|
|
_ = createPendingAuthorization(t, sa, ident, expiresB)
|
|
|
|
earliest := fc.Now().Add(-time.Hour).UTC()
|
|
latest := fc.Now().Add(time.Hour * 5).UTC()
|
|
count, err := sa.CountInvalidAuthorizations2(context.Background(), &sapb.CountInvalidAuthorizationsRequest{
|
|
RegistrationID: reg.Id,
|
|
DnsName: ident,
|
|
Range: &sapb.Range{
|
|
Earliest: timestamppb.New(earliest),
|
|
Latest: timestamppb.New(latest),
|
|
},
|
|
})
|
|
test.AssertNotError(t, err, "sa.CountInvalidAuthorizations2 failed")
|
|
test.AssertEquals(t, count.Count, int64(1))
|
|
}
|
|
|
|
func TestGetValidAuthorizations2(t *testing.T) {
|
|
sa, fc, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
// Create a valid authorization
|
|
ident := "aaa"
|
|
expires := fc.Now().Add(time.Hour).UTC()
|
|
attemptedAt := fc.Now()
|
|
authzID := createFinalizedAuthorization(t, sa, ident, expires, "valid", attemptedAt)
|
|
|
|
now := fc.Now().UTC()
|
|
regID := int64(1)
|
|
authzs, err := sa.GetValidAuthorizations2(context.Background(), &sapb.GetValidAuthorizationsRequest{
|
|
DnsNames: []string{
|
|
"aaa",
|
|
"bbb",
|
|
},
|
|
RegistrationID: regID,
|
|
ValidUntil: timestamppb.New(now),
|
|
})
|
|
test.AssertNotError(t, err, "sa.GetValidAuthorizations2 failed")
|
|
test.AssertEquals(t, len(authzs.Authzs), 1)
|
|
test.AssertEquals(t, authzs.Authzs[0].DnsName, ident)
|
|
test.AssertEquals(t, authzs.Authzs[0].Id, fmt.Sprintf("%d", authzID))
|
|
}
|
|
|
|
func TestGetOrderExpired(t *testing.T) {
|
|
sa, fc, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
fc.Add(time.Hour * 5)
|
|
now := fc.Now()
|
|
reg := createWorkingRegistration(t, sa)
|
|
order, err := sa.NewOrderAndAuthzs(context.Background(), &sapb.NewOrderAndAuthzsRequest{
|
|
NewOrder: &sapb.NewOrderRequest{
|
|
RegistrationID: reg.Id,
|
|
Expires: timestamppb.New(now.Add(-time.Hour)),
|
|
DnsNames: []string{"example.com"},
|
|
V2Authorizations: []int64{666},
|
|
},
|
|
})
|
|
test.AssertNotError(t, err, "NewOrderAndAuthzs failed")
|
|
_, err = sa.GetOrder(context.Background(), &sapb.OrderRequest{
|
|
Id: order.Id,
|
|
})
|
|
test.AssertError(t, err, "GetOrder didn't fail for an expired order")
|
|
test.AssertErrorIs(t, err, berrors.NotFound)
|
|
}
|
|
|
|
func TestBlockedKey(t *testing.T) {
|
|
sa, _, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
hashA := make([]byte, 32)
|
|
hashA[0] = 1
|
|
hashB := make([]byte, 32)
|
|
hashB[0] = 2
|
|
|
|
added := time.Now()
|
|
source := "API"
|
|
_, err := sa.AddBlockedKey(context.Background(), &sapb.AddBlockedKeyRequest{
|
|
KeyHash: hashA,
|
|
Added: timestamppb.New(added),
|
|
Source: source,
|
|
})
|
|
test.AssertNotError(t, err, "AddBlockedKey failed")
|
|
_, err = sa.AddBlockedKey(context.Background(), &sapb.AddBlockedKeyRequest{
|
|
KeyHash: hashA,
|
|
Added: timestamppb.New(added),
|
|
Source: source,
|
|
})
|
|
test.AssertNotError(t, err, "AddBlockedKey failed with duplicate insert")
|
|
|
|
comment := "testing comments"
|
|
_, err = sa.AddBlockedKey(context.Background(), &sapb.AddBlockedKeyRequest{
|
|
KeyHash: hashB,
|
|
Added: timestamppb.New(added),
|
|
Source: source,
|
|
Comment: comment,
|
|
})
|
|
test.AssertNotError(t, err, "AddBlockedKey failed")
|
|
|
|
exists, err := sa.KeyBlocked(context.Background(), &sapb.SPKIHash{
|
|
KeyHash: hashA,
|
|
})
|
|
test.AssertNotError(t, err, "KeyBlocked failed")
|
|
test.Assert(t, exists != nil, "*sapb.Exists is nil")
|
|
test.Assert(t, exists.Exists, "KeyBlocked returned false for blocked key")
|
|
exists, err = sa.KeyBlocked(context.Background(), &sapb.SPKIHash{
|
|
KeyHash: hashB,
|
|
})
|
|
test.AssertNotError(t, err, "KeyBlocked failed")
|
|
test.Assert(t, exists != nil, "*sapb.Exists is nil")
|
|
test.Assert(t, exists.Exists, "KeyBlocked returned false for blocked key")
|
|
exists, err = sa.KeyBlocked(context.Background(), &sapb.SPKIHash{
|
|
KeyHash: []byte{5},
|
|
})
|
|
test.AssertNotError(t, err, "KeyBlocked failed")
|
|
test.Assert(t, exists != nil, "*sapb.Exists is nil")
|
|
test.Assert(t, !exists.Exists, "KeyBlocked returned true for non-blocked key")
|
|
}
|
|
|
|
func TestAddBlockedKeyUnknownSource(t *testing.T) {
|
|
sa, fc, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
_, err := sa.AddBlockedKey(context.Background(), &sapb.AddBlockedKeyRequest{
|
|
KeyHash: []byte{1, 2, 3},
|
|
Added: timestamppb.New(fc.Now()),
|
|
Source: "heyo",
|
|
})
|
|
test.AssertError(t, err, "AddBlockedKey didn't fail with unknown source")
|
|
test.AssertEquals(t, err.Error(), "unknown source")
|
|
}
|
|
|
|
func TestBlockedKeyRevokedBy(t *testing.T) {
|
|
sa, fc, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
now := fc.Now()
|
|
_, err := sa.AddBlockedKey(context.Background(), &sapb.AddBlockedKeyRequest{
|
|
KeyHash: []byte{1},
|
|
Added: timestamppb.New(now),
|
|
Source: "API",
|
|
})
|
|
test.AssertNotError(t, err, "AddBlockedKey failed")
|
|
|
|
_, err = sa.AddBlockedKey(context.Background(), &sapb.AddBlockedKeyRequest{
|
|
KeyHash: []byte{2},
|
|
Added: timestamppb.New(now),
|
|
Source: "API",
|
|
RevokedBy: 1,
|
|
})
|
|
test.AssertNotError(t, err, "AddBlockedKey failed")
|
|
}
|
|
|
|
func TestIncidentsForSerial(t *testing.T) {
|
|
sa, _, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
testSADbMap, err := DBMapForTest(vars.DBConnSAFullPerms)
|
|
test.AssertNotError(t, err, "Couldn't create test dbMap")
|
|
|
|
testIncidentsDbMap, err := DBMapForTest(vars.DBConnIncidentsFullPerms)
|
|
test.AssertNotError(t, err, "Couldn't create test dbMap")
|
|
defer test.ResetIncidentsTestDatabase(t)
|
|
|
|
weekAgo := sa.clk.Now().Add(-time.Hour * 24 * 7)
|
|
|
|
// Add a disabled incident.
|
|
err = testSADbMap.Insert(ctx, &incidentModel{
|
|
SerialTable: "incident_foo",
|
|
URL: "https://example.com/foo-incident",
|
|
RenewBy: sa.clk.Now().Add(time.Hour * 24 * 7),
|
|
Enabled: false,
|
|
})
|
|
test.AssertNotError(t, err, "Failed to insert disabled incident")
|
|
|
|
// No incidents are enabled, so this should return in error.
|
|
result, err := sa.IncidentsForSerial(context.Background(), &sapb.Serial{Serial: "1337"})
|
|
test.AssertNotError(t, err, "fetching from no incidents")
|
|
test.AssertEquals(t, len(result.Incidents), 0)
|
|
|
|
// Add an enabled incident.
|
|
err = testSADbMap.Insert(ctx, &incidentModel{
|
|
SerialTable: "incident_bar",
|
|
URL: "https://example.com/test-incident",
|
|
RenewBy: sa.clk.Now().Add(time.Hour * 24 * 7),
|
|
Enabled: true,
|
|
})
|
|
test.AssertNotError(t, err, "Failed to insert enabled incident")
|
|
|
|
// Add a row to the incident table with serial '1338'.
|
|
one := int64(1)
|
|
affectedCertA := incidentSerialModel{
|
|
Serial: "1338",
|
|
RegistrationID: &one,
|
|
OrderID: &one,
|
|
LastNoticeSent: &weekAgo,
|
|
}
|
|
_, err = testIncidentsDbMap.ExecContext(ctx,
|
|
fmt.Sprintf("INSERT INTO incident_bar (%s) VALUES ('%s', %d, %d, '%s')",
|
|
"serial, registrationID, orderID, lastNoticeSent",
|
|
affectedCertA.Serial,
|
|
affectedCertA.RegistrationID,
|
|
affectedCertA.OrderID,
|
|
affectedCertA.LastNoticeSent.Format(time.DateTime),
|
|
),
|
|
)
|
|
test.AssertNotError(t, err, "Error while inserting row for '1338' into incident table")
|
|
|
|
// The incident table should not contain a row with serial '1337'.
|
|
result, err = sa.IncidentsForSerial(context.Background(), &sapb.Serial{Serial: "1337"})
|
|
test.AssertNotError(t, err, "fetching from one incident")
|
|
test.AssertEquals(t, len(result.Incidents), 0)
|
|
|
|
// Add a row to the incident table with serial '1337'.
|
|
two := int64(2)
|
|
affectedCertB := incidentSerialModel{
|
|
Serial: "1337",
|
|
RegistrationID: &two,
|
|
OrderID: &two,
|
|
LastNoticeSent: &weekAgo,
|
|
}
|
|
_, err = testIncidentsDbMap.ExecContext(ctx,
|
|
fmt.Sprintf("INSERT INTO incident_bar (%s) VALUES ('%s', %d, %d, '%s')",
|
|
"serial, registrationID, orderID, lastNoticeSent",
|
|
affectedCertB.Serial,
|
|
affectedCertB.RegistrationID,
|
|
affectedCertB.OrderID,
|
|
affectedCertB.LastNoticeSent.Format(time.DateTime),
|
|
),
|
|
)
|
|
test.AssertNotError(t, err, "Error while inserting row for '1337' into incident table")
|
|
|
|
// The incident table should now contain a row with serial '1337'.
|
|
result, err = sa.IncidentsForSerial(context.Background(), &sapb.Serial{Serial: "1337"})
|
|
test.AssertNotError(t, err, "Failed to retrieve incidents for serial")
|
|
test.AssertEquals(t, len(result.Incidents), 1)
|
|
}
|
|
|
|
func TestSerialsForIncident(t *testing.T) {
|
|
sa, _, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
testIncidentsDbMap, err := DBMapForTest(vars.DBConnIncidentsFullPerms)
|
|
test.AssertNotError(t, err, "Couldn't create test dbMap")
|
|
defer test.ResetIncidentsTestDatabase(t)
|
|
|
|
// Request serials from a malformed incident table name.
|
|
mockServerStream := &fakeServerStream[sapb.IncidentSerial]{}
|
|
err = sa.SerialsForIncident(
|
|
&sapb.SerialsForIncidentRequest{
|
|
IncidentTable: "incidesnt_Baz",
|
|
},
|
|
mockServerStream,
|
|
)
|
|
test.AssertError(t, err, "Expected error for malformed table name")
|
|
test.AssertContains(t, err.Error(), "malformed table name \"incidesnt_Baz\"")
|
|
|
|
// Request serials from another malformed incident table name.
|
|
mockServerStream = &fakeServerStream[sapb.IncidentSerial]{}
|
|
longTableName := "incident_l" + strings.Repeat("o", 1000) + "ng"
|
|
err = sa.SerialsForIncident(
|
|
&sapb.SerialsForIncidentRequest{
|
|
IncidentTable: longTableName,
|
|
},
|
|
mockServerStream,
|
|
)
|
|
test.AssertError(t, err, "Expected error for long table name")
|
|
test.AssertContains(t, err.Error(), fmt.Sprintf("malformed table name %q", longTableName))
|
|
|
|
// Request serials for an incident table which doesn't exists.
|
|
mockServerStream = &fakeServerStream[sapb.IncidentSerial]{}
|
|
err = sa.SerialsForIncident(
|
|
&sapb.SerialsForIncidentRequest{
|
|
IncidentTable: "incident_baz",
|
|
},
|
|
mockServerStream,
|
|
)
|
|
test.AssertError(t, err, "Expected error for nonexistent table name")
|
|
|
|
// Assert that the error is a MySQL error so we can inspect the error code.
|
|
var mysqlErr *mysql.MySQLError
|
|
if errors.As(err, &mysqlErr) {
|
|
// We expect the error code to be 1146 (ER_NO_SUCH_TABLE):
|
|
// https://mariadb.com/kb/en/mariadb-error-codes/
|
|
test.AssertEquals(t, mysqlErr.Number, uint16(1146))
|
|
} else {
|
|
t.Fatalf("Expected MySQL Error 1146 (ER_NO_SUCH_TABLE) from Recv(), got %q", err)
|
|
}
|
|
|
|
// Request serials from table 'incident_foo', which we expect to exist but
|
|
// be empty.
|
|
stream := make(chan *sapb.IncidentSerial)
|
|
mockServerStream = &fakeServerStream[sapb.IncidentSerial]{output: stream}
|
|
go func() {
|
|
err = sa.SerialsForIncident(
|
|
&sapb.SerialsForIncidentRequest{
|
|
IncidentTable: "incident_foo",
|
|
},
|
|
mockServerStream,
|
|
)
|
|
close(stream) // Let our main test thread continue.
|
|
}()
|
|
for range stream {
|
|
t.Fatal("No serials should have been written to this stream")
|
|
}
|
|
test.AssertNotError(t, err, "Error calling SerialsForIncident on empty table")
|
|
|
|
// Add 4 rows of incident serials to 'incident_foo'.
|
|
expectedSerials := map[string]bool{
|
|
"1335": true, "1336": true, "1337": true, "1338": true,
|
|
}
|
|
for i := range expectedSerials {
|
|
randInt := func() int64 { return mrand.Int64() }
|
|
_, err := testIncidentsDbMap.ExecContext(ctx,
|
|
fmt.Sprintf("INSERT INTO incident_foo (%s) VALUES ('%s', %d, %d, '%s')",
|
|
"serial, registrationID, orderID, lastNoticeSent",
|
|
i,
|
|
randInt(),
|
|
randInt(),
|
|
sa.clk.Now().Add(time.Hour*24*7).Format(time.DateTime),
|
|
),
|
|
)
|
|
test.AssertNotError(t, err, fmt.Sprintf("Error while inserting row for '%s' into incident table", i))
|
|
}
|
|
|
|
// Request all 4 serials from the incident table we just added entries to.
|
|
stream = make(chan *sapb.IncidentSerial)
|
|
mockServerStream = &fakeServerStream[sapb.IncidentSerial]{output: stream}
|
|
go func() {
|
|
err = sa.SerialsForIncident(
|
|
&sapb.SerialsForIncidentRequest{
|
|
IncidentTable: "incident_foo",
|
|
},
|
|
mockServerStream,
|
|
)
|
|
close(stream)
|
|
}()
|
|
receivedSerials := make(map[string]bool)
|
|
for serial := range stream {
|
|
if len(receivedSerials) > 4 {
|
|
t.Fatal("Received too many serials")
|
|
}
|
|
if _, ok := receivedSerials[serial.Serial]; ok {
|
|
t.Fatalf("Received serial %q more than once", serial.Serial)
|
|
}
|
|
receivedSerials[serial.Serial] = true
|
|
}
|
|
test.AssertDeepEquals(t, receivedSerials, map[string]bool{
|
|
"1335": true, "1336": true, "1337": true, "1338": true,
|
|
})
|
|
test.AssertNotError(t, err, "Error getting serials for incident")
|
|
}
|
|
|
|
func TestGetRevokedCerts(t *testing.T) {
|
|
sa, _, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
// Add a cert to the DB to test with. We use AddPrecertificate because it sets
|
|
// up the certificateStatus row we need. This particular cert has a notAfter
|
|
// date of Mar 6 2023, and we lie about its IssuerNameID to make things easy.
|
|
reg := createWorkingRegistration(t, sa)
|
|
eeCert, err := core.LoadCert("../test/hierarchy/ee-e1.cert.pem")
|
|
test.AssertNotError(t, err, "failed to load test cert")
|
|
_, err = sa.AddSerial(ctx, &sapb.AddSerialRequest{
|
|
RegID: reg.Id,
|
|
Serial: core.SerialToString(eeCert.SerialNumber),
|
|
Created: timestamppb.New(eeCert.NotBefore),
|
|
Expires: timestamppb.New(eeCert.NotAfter),
|
|
})
|
|
test.AssertNotError(t, err, "failed to add test serial")
|
|
_, err = sa.AddPrecertificate(ctx, &sapb.AddCertificateRequest{
|
|
Der: eeCert.Raw,
|
|
RegID: reg.Id,
|
|
Issued: timestamppb.New(eeCert.NotBefore),
|
|
IssuerNameID: 1,
|
|
})
|
|
test.AssertNotError(t, err, "failed to add test cert")
|
|
|
|
// Check that it worked.
|
|
status, err := sa.GetCertificateStatus(
|
|
ctx, &sapb.Serial{Serial: core.SerialToString(eeCert.SerialNumber)})
|
|
test.AssertNotError(t, err, "GetCertificateStatus failed")
|
|
test.AssertEquals(t, core.OCSPStatus(status.Status), core.OCSPStatusGood)
|
|
|
|
// Here's a little helper func we'll use to call GetRevokedCerts and count
|
|
// how many results it returned.
|
|
countRevokedCerts := func(req *sapb.GetRevokedCertsRequest) (int, error) {
|
|
stream := make(chan *corepb.CRLEntry)
|
|
mockServerStream := &fakeServerStream[corepb.CRLEntry]{output: stream}
|
|
var err error
|
|
go func() {
|
|
err = sa.GetRevokedCerts(req, mockServerStream)
|
|
close(stream)
|
|
}()
|
|
entriesReceived := 0
|
|
for range stream {
|
|
entriesReceived++
|
|
}
|
|
return entriesReceived, err
|
|
}
|
|
|
|
// Asking for revoked certs now should return no results.
|
|
expiresAfter := time.Date(2023, time.March, 1, 0, 0, 0, 0, time.UTC)
|
|
expiresBefore := time.Date(2023, time.April, 1, 0, 0, 0, 0, time.UTC)
|
|
revokedBefore := time.Date(2023, time.April, 1, 0, 0, 0, 0, time.UTC)
|
|
count, err := countRevokedCerts(&sapb.GetRevokedCertsRequest{
|
|
IssuerNameID: 1,
|
|
ExpiresAfter: timestamppb.New(expiresAfter),
|
|
ExpiresBefore: timestamppb.New(expiresBefore),
|
|
RevokedBefore: timestamppb.New(revokedBefore),
|
|
})
|
|
test.AssertNotError(t, err, "zero rows shouldn't result in error")
|
|
test.AssertEquals(t, count, 0)
|
|
|
|
// Revoke the certificate.
|
|
date := time.Date(2023, time.January, 1, 0, 0, 0, 0, time.UTC)
|
|
_, err = sa.RevokeCertificate(context.Background(), &sapb.RevokeCertificateRequest{
|
|
IssuerID: 1,
|
|
Serial: core.SerialToString(eeCert.SerialNumber),
|
|
Date: timestamppb.New(date),
|
|
Reason: 1,
|
|
Response: []byte{1, 2, 3},
|
|
})
|
|
test.AssertNotError(t, err, "failed to revoke test cert")
|
|
|
|
// Asking for revoked certs now should return one result.
|
|
count, err = countRevokedCerts(&sapb.GetRevokedCertsRequest{
|
|
IssuerNameID: 1,
|
|
ExpiresAfter: timestamppb.New(expiresAfter),
|
|
ExpiresBefore: timestamppb.New(expiresBefore),
|
|
RevokedBefore: timestamppb.New(revokedBefore),
|
|
})
|
|
test.AssertNotError(t, err, "normal usage shouldn't result in error")
|
|
test.AssertEquals(t, count, 1)
|
|
|
|
// Asking for revoked certs with an old RevokedBefore should return no results.
|
|
expiresAfter = time.Date(2023, time.March, 1, 0, 0, 0, 0, time.UTC)
|
|
expiresBefore = time.Date(2023, time.April, 1, 0, 0, 0, 0, time.UTC)
|
|
revokedBefore = time.Date(2020, time.March, 1, 0, 0, 0, 0, time.UTC)
|
|
count, err = countRevokedCerts(&sapb.GetRevokedCertsRequest{
|
|
IssuerNameID: 1,
|
|
ExpiresAfter: timestamppb.New(expiresAfter),
|
|
ExpiresBefore: timestamppb.New(expiresBefore),
|
|
RevokedBefore: timestamppb.New(revokedBefore),
|
|
})
|
|
test.AssertNotError(t, err, "zero rows shouldn't result in error")
|
|
test.AssertEquals(t, count, 0)
|
|
|
|
// Asking for revoked certs in a time period that does not cover this cert's
|
|
// notAfter timestamp should return zero results.
|
|
expiresAfter = time.Date(2022, time.March, 1, 0, 0, 0, 0, time.UTC)
|
|
expiresBefore = time.Date(2022, time.April, 1, 0, 0, 0, 0, time.UTC)
|
|
revokedBefore = time.Date(2023, time.April, 1, 0, 0, 0, 0, time.UTC)
|
|
count, err = countRevokedCerts(&sapb.GetRevokedCertsRequest{
|
|
IssuerNameID: 1,
|
|
ExpiresAfter: timestamppb.New(expiresAfter),
|
|
ExpiresBefore: timestamppb.New(expiresBefore),
|
|
RevokedBefore: timestamppb.New(revokedBefore),
|
|
})
|
|
test.AssertNotError(t, err, "zero rows shouldn't result in error")
|
|
test.AssertEquals(t, count, 0)
|
|
|
|
// Asking for revoked certs from a different issuer should return zero results.
|
|
count, err = countRevokedCerts(&sapb.GetRevokedCertsRequest{
|
|
IssuerNameID: 1,
|
|
ExpiresAfter: timestamppb.New(time.Date(2022, time.March, 1, 0, 0, 0, 0, time.UTC)),
|
|
ExpiresBefore: timestamppb.New(time.Date(2022, time.April, 1, 0, 0, 0, 0, time.UTC)),
|
|
RevokedBefore: timestamppb.New(time.Date(2023, time.April, 1, 0, 0, 0, 0, time.UTC)),
|
|
})
|
|
test.AssertNotError(t, err, "zero rows shouldn't result in error")
|
|
test.AssertEquals(t, count, 0)
|
|
}
|
|
|
|
func TestGetRevokedCertsByShard(t *testing.T) {
|
|
if os.Getenv("BOULDER_CONFIG_DIR") != "test/config-next" {
|
|
t.Skip("Test requires revokedCertificates database table")
|
|
}
|
|
|
|
sa, _, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
// Add a cert to the DB to test with. We use AddPrecertificate because it sets
|
|
// up the certificateStatus row we need. This particular cert has a notAfter
|
|
// date of Mar 6 2023, and we lie about its IssuerNameID to make things easy.
|
|
reg := createWorkingRegistration(t, sa)
|
|
eeCert, err := core.LoadCert("../test/hierarchy/ee-e1.cert.pem")
|
|
test.AssertNotError(t, err, "failed to load test cert")
|
|
_, err = sa.AddSerial(ctx, &sapb.AddSerialRequest{
|
|
RegID: reg.Id,
|
|
Serial: core.SerialToString(eeCert.SerialNumber),
|
|
Created: timestamppb.New(eeCert.NotBefore),
|
|
Expires: timestamppb.New(eeCert.NotAfter),
|
|
})
|
|
test.AssertNotError(t, err, "failed to add test serial")
|
|
_, err = sa.AddPrecertificate(ctx, &sapb.AddCertificateRequest{
|
|
Der: eeCert.Raw,
|
|
RegID: reg.Id,
|
|
Issued: timestamppb.New(eeCert.NotBefore),
|
|
IssuerNameID: 1,
|
|
})
|
|
test.AssertNotError(t, err, "failed to add test cert")
|
|
|
|
// Check that it worked.
|
|
status, err := sa.GetCertificateStatus(
|
|
ctx, &sapb.Serial{Serial: core.SerialToString(eeCert.SerialNumber)})
|
|
test.AssertNotError(t, err, "GetCertificateStatus failed")
|
|
test.AssertEquals(t, core.OCSPStatus(status.Status), core.OCSPStatusGood)
|
|
|
|
// Here's a little helper func we'll use to call GetRevokedCerts and count
|
|
// how many results it returned.
|
|
countRevokedCerts := func(req *sapb.GetRevokedCertsRequest) (int, error) {
|
|
stream := make(chan *corepb.CRLEntry)
|
|
mockServerStream := &fakeServerStream[corepb.CRLEntry]{output: stream}
|
|
var err error
|
|
go func() {
|
|
err = sa.GetRevokedCerts(req, mockServerStream)
|
|
close(stream)
|
|
}()
|
|
entriesReceived := 0
|
|
for range stream {
|
|
entriesReceived++
|
|
}
|
|
return entriesReceived, err
|
|
}
|
|
|
|
// Asking for revoked certs now should return no results.
|
|
expiresAfter := time.Date(2023, time.March, 1, 0, 0, 0, 0, time.UTC)
|
|
revokedBefore := time.Date(2023, time.April, 1, 0, 0, 0, 0, time.UTC)
|
|
count, err := countRevokedCerts(&sapb.GetRevokedCertsRequest{
|
|
IssuerNameID: 1,
|
|
ShardIdx: 9,
|
|
ExpiresAfter: timestamppb.New(expiresAfter),
|
|
RevokedBefore: timestamppb.New(revokedBefore),
|
|
})
|
|
test.AssertNotError(t, err, "zero rows shouldn't result in error")
|
|
test.AssertEquals(t, count, 0)
|
|
|
|
// Revoke the certificate, providing the ShardIdx so it gets written into
|
|
// both the certificateStatus and revokedCertificates tables.
|
|
date := time.Date(2023, time.January, 1, 0, 0, 0, 0, time.UTC)
|
|
_, err = sa.RevokeCertificate(context.Background(), &sapb.RevokeCertificateRequest{
|
|
IssuerID: 1,
|
|
Serial: core.SerialToString(eeCert.SerialNumber),
|
|
Date: timestamppb.New(date),
|
|
Reason: 1,
|
|
Response: []byte{1, 2, 3},
|
|
ShardIdx: 9,
|
|
})
|
|
test.AssertNotError(t, err, "failed to revoke test cert")
|
|
|
|
// Check that it worked in the most basic way.
|
|
c, err := sa.dbMap.SelectNullInt(
|
|
ctx, "SELECT count(*) FROM revokedCertificates")
|
|
test.AssertNotError(t, err, "SELECT from revokedCertificates failed")
|
|
test.Assert(t, c.Valid, "SELECT from revokedCertificates got no result")
|
|
test.AssertEquals(t, c.Int64, int64(1))
|
|
|
|
// Asking for revoked certs now should return one result.
|
|
expiresAfter = time.Date(2023, time.March, 1, 0, 0, 0, 0, time.UTC)
|
|
revokedBefore = time.Date(2023, time.April, 1, 0, 0, 0, 0, time.UTC)
|
|
count, err = countRevokedCerts(&sapb.GetRevokedCertsRequest{
|
|
IssuerNameID: 1,
|
|
ShardIdx: 9,
|
|
ExpiresAfter: timestamppb.New(expiresAfter),
|
|
RevokedBefore: timestamppb.New(revokedBefore),
|
|
})
|
|
test.AssertNotError(t, err, "normal usage shouldn't result in error")
|
|
test.AssertEquals(t, count, 1)
|
|
|
|
// Asking for revoked certs from a different issuer should return zero results.
|
|
expiresAfter = time.Date(2023, time.March, 1, 0, 0, 0, 0, time.UTC)
|
|
revokedBefore = time.Date(2023, time.April, 1, 0, 0, 0, 0, time.UTC)
|
|
count, err = countRevokedCerts(&sapb.GetRevokedCertsRequest{
|
|
IssuerNameID: 2,
|
|
ShardIdx: 9,
|
|
ExpiresAfter: timestamppb.New(expiresAfter),
|
|
RevokedBefore: timestamppb.New(revokedBefore),
|
|
})
|
|
test.AssertNotError(t, err, "zero rows shouldn't result in error")
|
|
test.AssertEquals(t, count, 0)
|
|
|
|
// Asking for revoked certs from a different shard should return zero results.
|
|
expiresAfter = time.Date(2023, time.March, 1, 0, 0, 0, 0, time.UTC)
|
|
revokedBefore = time.Date(2023, time.April, 1, 0, 0, 0, 0, time.UTC)
|
|
count, err = countRevokedCerts(&sapb.GetRevokedCertsRequest{
|
|
IssuerNameID: 1,
|
|
ShardIdx: 8,
|
|
ExpiresAfter: timestamppb.New(expiresAfter),
|
|
RevokedBefore: timestamppb.New(revokedBefore),
|
|
})
|
|
test.AssertNotError(t, err, "zero rows shouldn't result in error")
|
|
test.AssertEquals(t, count, 0)
|
|
|
|
// Asking for revoked certs with an old RevokedBefore should return no results.
|
|
expiresAfter = time.Date(2023, time.March, 1, 0, 0, 0, 0, time.UTC)
|
|
revokedBefore = time.Date(2020, time.March, 1, 0, 0, 0, 0, time.UTC)
|
|
count, err = countRevokedCerts(&sapb.GetRevokedCertsRequest{
|
|
IssuerNameID: 1,
|
|
ShardIdx: 9,
|
|
ExpiresAfter: timestamppb.New(expiresAfter),
|
|
RevokedBefore: timestamppb.New(revokedBefore),
|
|
})
|
|
test.AssertNotError(t, err, "zero rows shouldn't result in error")
|
|
test.AssertEquals(t, count, 0)
|
|
}
|
|
|
|
func TestGetMaxExpiration(t *testing.T) {
|
|
sa, _, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
// Add a cert to the DB to test with. We use AddPrecertificate because it sets
|
|
// up the certificateStatus row we need. This particular cert has a notAfter
|
|
// date of Mar 6 2023, and we lie about its IssuerNameID to make things easy.
|
|
reg := createWorkingRegistration(t, sa)
|
|
eeCert, err := core.LoadCert("../test/hierarchy/ee-e1.cert.pem")
|
|
test.AssertNotError(t, err, "failed to load test cert")
|
|
_, err = sa.AddPrecertificate(ctx, &sapb.AddCertificateRequest{
|
|
Der: eeCert.Raw,
|
|
RegID: reg.Id,
|
|
Issued: timestamppb.New(eeCert.NotBefore),
|
|
IssuerNameID: 1,
|
|
})
|
|
test.AssertNotError(t, err, "failed to add test cert")
|
|
|
|
lastExpiry, err := sa.GetMaxExpiration(context.Background(), &emptypb.Empty{})
|
|
test.AssertNotError(t, err, "getting last expriy should succeed")
|
|
test.Assert(t, lastExpiry.AsTime().Equal(eeCert.NotAfter), "times should be equal")
|
|
test.AssertEquals(t, timestamppb.New(eeCert.NotBefore).AsTime(), eeCert.NotBefore)
|
|
}
|
|
|
|
func TestLeaseOldestCRLShard(t *testing.T) {
|
|
sa, clk, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
// Create 8 shards: 4 for each of 2 issuers. For each issuer, one shard is
|
|
// currently leased, three are available, and one of those failed to update.
|
|
_, err := sa.dbMap.ExecContext(ctx,
|
|
`INSERT INTO crlShards (issuerID, idx, thisUpdate, nextUpdate, leasedUntil) VALUES
|
|
(1, 0, ?, ?, ?),
|
|
(1, 1, ?, ?, ?),
|
|
(1, 2, ?, ?, ?),
|
|
(1, 3, NULL, NULL, ?),
|
|
(2, 0, ?, ?, ?),
|
|
(2, 1, ?, ?, ?),
|
|
(2, 2, ?, ?, ?),
|
|
(2, 3, NULL, NULL, ?);`,
|
|
clk.Now().Add(-7*24*time.Hour), clk.Now().Add(3*24*time.Hour), clk.Now().Add(time.Hour),
|
|
clk.Now().Add(-6*24*time.Hour), clk.Now().Add(4*24*time.Hour), clk.Now().Add(-6*24*time.Hour),
|
|
clk.Now().Add(-5*24*time.Hour), clk.Now().Add(5*24*time.Hour), clk.Now().Add(-5*24*time.Hour),
|
|
clk.Now().Add(-4*24*time.Hour),
|
|
clk.Now().Add(-7*24*time.Hour), clk.Now().Add(3*24*time.Hour), clk.Now().Add(time.Hour),
|
|
clk.Now().Add(-6*24*time.Hour), clk.Now().Add(4*24*time.Hour), clk.Now().Add(-6*24*time.Hour),
|
|
clk.Now().Add(-5*24*time.Hour), clk.Now().Add(5*24*time.Hour), clk.Now().Add(-5*24*time.Hour),
|
|
clk.Now().Add(-4*24*time.Hour),
|
|
)
|
|
test.AssertNotError(t, err, "setting up test shards")
|
|
|
|
until := clk.Now().Add(time.Hour).Truncate(time.Second).UTC()
|
|
var untilModel struct {
|
|
LeasedUntil time.Time `db:"leasedUntil"`
|
|
}
|
|
|
|
// Leasing from a fully-leased subset should fail.
|
|
_, err = sa.leaseOldestCRLShard(
|
|
context.Background(),
|
|
&sapb.LeaseCRLShardRequest{
|
|
IssuerNameID: 1,
|
|
MinShardIdx: 0,
|
|
MaxShardIdx: 0,
|
|
Until: timestamppb.New(until),
|
|
},
|
|
)
|
|
test.AssertError(t, err, "leasing when all shards are leased")
|
|
|
|
// Leasing any known shard should return the never-before-leased one (3).
|
|
res, err := sa.leaseOldestCRLShard(
|
|
context.Background(),
|
|
&sapb.LeaseCRLShardRequest{
|
|
IssuerNameID: 1,
|
|
MinShardIdx: 0,
|
|
MaxShardIdx: 3,
|
|
Until: timestamppb.New(until),
|
|
},
|
|
)
|
|
test.AssertNotError(t, err, "leasing available shard")
|
|
test.AssertEquals(t, res.IssuerNameID, int64(1))
|
|
test.AssertEquals(t, res.ShardIdx, int64(3))
|
|
|
|
err = sa.dbMap.SelectOne(
|
|
ctx,
|
|
&untilModel,
|
|
`SELECT leasedUntil FROM crlShards WHERE issuerID = ? AND idx = ? LIMIT 1`,
|
|
res.IssuerNameID,
|
|
res.ShardIdx,
|
|
)
|
|
test.AssertNotError(t, err, "getting updated lease timestamp")
|
|
test.Assert(t, untilModel.LeasedUntil.Equal(until), "checking updated lease timestamp")
|
|
|
|
// Leasing any known shard *again* should now return the oldest one (1).
|
|
res, err = sa.leaseOldestCRLShard(
|
|
context.Background(),
|
|
&sapb.LeaseCRLShardRequest{
|
|
IssuerNameID: 1,
|
|
MinShardIdx: 0,
|
|
MaxShardIdx: 3,
|
|
Until: timestamppb.New(until),
|
|
},
|
|
)
|
|
test.AssertNotError(t, err, "leasing available shard")
|
|
test.AssertEquals(t, res.IssuerNameID, int64(1))
|
|
test.AssertEquals(t, res.ShardIdx, int64(1))
|
|
|
|
err = sa.dbMap.SelectOne(
|
|
ctx,
|
|
&untilModel,
|
|
`SELECT leasedUntil FROM crlShards WHERE issuerID = ? AND idx = ? LIMIT 1`,
|
|
res.IssuerNameID,
|
|
res.ShardIdx,
|
|
)
|
|
test.AssertNotError(t, err, "getting updated lease timestamp")
|
|
test.Assert(t, untilModel.LeasedUntil.Equal(until), "checking updated lease timestamp")
|
|
|
|
// Leasing from a superset of known shards should succeed and return one of
|
|
// the previously-unknown shards.
|
|
res, err = sa.leaseOldestCRLShard(
|
|
context.Background(),
|
|
&sapb.LeaseCRLShardRequest{
|
|
IssuerNameID: 2,
|
|
MinShardIdx: 0,
|
|
MaxShardIdx: 7,
|
|
Until: timestamppb.New(until),
|
|
},
|
|
)
|
|
test.AssertNotError(t, err, "leasing available shard")
|
|
test.AssertEquals(t, res.IssuerNameID, int64(2))
|
|
test.Assert(t, res.ShardIdx >= 4, "checking leased index")
|
|
test.Assert(t, res.ShardIdx <= 7, "checking leased index")
|
|
|
|
err = sa.dbMap.SelectOne(
|
|
ctx,
|
|
&untilModel,
|
|
`SELECT leasedUntil FROM crlShards WHERE issuerID = ? AND idx = ? LIMIT 1`,
|
|
res.IssuerNameID,
|
|
res.ShardIdx,
|
|
)
|
|
test.AssertNotError(t, err, "getting updated lease timestamp")
|
|
test.Assert(t, untilModel.LeasedUntil.Equal(until), "checking updated lease timestamp")
|
|
}
|
|
|
|
func TestLeaseSpecificCRLShard(t *testing.T) {
|
|
sa, clk, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
// Create 8 shards: 4 for each of 2 issuers. For each issuer, one shard is
|
|
// currently leased, three are available, and one of those failed to update.
|
|
_, err := sa.dbMap.ExecContext(ctx,
|
|
`INSERT INTO crlShards (issuerID, idx, thisUpdate, nextUpdate, leasedUntil) VALUES
|
|
(1, 0, ?, ?, ?),
|
|
(1, 1, ?, ?, ?),
|
|
(1, 2, ?, ?, ?),
|
|
(1, 3, NULL, NULL, ?),
|
|
(2, 0, ?, ?, ?),
|
|
(2, 1, ?, ?, ?),
|
|
(2, 2, ?, ?, ?),
|
|
(2, 3, NULL, NULL, ?);`,
|
|
clk.Now().Add(-7*24*time.Hour), clk.Now().Add(3*24*time.Hour), clk.Now().Add(time.Hour),
|
|
clk.Now().Add(-6*24*time.Hour), clk.Now().Add(4*24*time.Hour), clk.Now().Add(-6*24*time.Hour),
|
|
clk.Now().Add(-5*24*time.Hour), clk.Now().Add(5*24*time.Hour), clk.Now().Add(-5*24*time.Hour),
|
|
clk.Now().Add(-4*24*time.Hour),
|
|
clk.Now().Add(-7*24*time.Hour), clk.Now().Add(3*24*time.Hour), clk.Now().Add(time.Hour),
|
|
clk.Now().Add(-6*24*time.Hour), clk.Now().Add(4*24*time.Hour), clk.Now().Add(-6*24*time.Hour),
|
|
clk.Now().Add(-5*24*time.Hour), clk.Now().Add(5*24*time.Hour), clk.Now().Add(-5*24*time.Hour),
|
|
clk.Now().Add(-4*24*time.Hour),
|
|
)
|
|
test.AssertNotError(t, err, "setting up test shards")
|
|
|
|
until := clk.Now().Add(time.Hour).Truncate(time.Second).UTC()
|
|
var untilModel struct {
|
|
LeasedUntil time.Time `db:"leasedUntil"`
|
|
}
|
|
|
|
// Leasing an unleased shard should work.
|
|
res, err := sa.leaseSpecificCRLShard(
|
|
context.Background(),
|
|
&sapb.LeaseCRLShardRequest{
|
|
IssuerNameID: 1,
|
|
MinShardIdx: 1,
|
|
MaxShardIdx: 1,
|
|
Until: timestamppb.New(until),
|
|
},
|
|
)
|
|
test.AssertNotError(t, err, "leasing available shard")
|
|
test.AssertEquals(t, res.IssuerNameID, int64(1))
|
|
test.AssertEquals(t, res.ShardIdx, int64(1))
|
|
|
|
err = sa.dbMap.SelectOne(
|
|
ctx,
|
|
&untilModel,
|
|
`SELECT leasedUntil FROM crlShards WHERE issuerID = ? AND idx = ? LIMIT 1`,
|
|
res.IssuerNameID,
|
|
res.ShardIdx,
|
|
)
|
|
test.AssertNotError(t, err, "getting updated lease timestamp")
|
|
test.Assert(t, untilModel.LeasedUntil.Equal(until), "checking updated lease timestamp")
|
|
|
|
// Leasing a never-before-leased shard should work.
|
|
res, err = sa.leaseSpecificCRLShard(
|
|
context.Background(),
|
|
&sapb.LeaseCRLShardRequest{
|
|
IssuerNameID: 2,
|
|
MinShardIdx: 3,
|
|
MaxShardIdx: 3,
|
|
Until: timestamppb.New(until),
|
|
},
|
|
)
|
|
test.AssertNotError(t, err, "leasing available shard")
|
|
test.AssertEquals(t, res.IssuerNameID, int64(2))
|
|
test.AssertEquals(t, res.ShardIdx, int64(3))
|
|
|
|
err = sa.dbMap.SelectOne(
|
|
ctx,
|
|
&untilModel,
|
|
`SELECT leasedUntil FROM crlShards WHERE issuerID = ? AND idx = ? LIMIT 1`,
|
|
res.IssuerNameID,
|
|
res.ShardIdx,
|
|
)
|
|
test.AssertNotError(t, err, "getting updated lease timestamp")
|
|
test.Assert(t, untilModel.LeasedUntil.Equal(until), "checking updated lease timestamp")
|
|
|
|
// Leasing a previously-unknown specific shard should work (to ease the
|
|
// transition into using leasing).
|
|
res, err = sa.leaseSpecificCRLShard(
|
|
context.Background(),
|
|
&sapb.LeaseCRLShardRequest{
|
|
IssuerNameID: 1,
|
|
MinShardIdx: 9,
|
|
MaxShardIdx: 9,
|
|
Until: timestamppb.New(until),
|
|
},
|
|
)
|
|
test.AssertNotError(t, err, "leasing unknown shard")
|
|
|
|
err = sa.dbMap.SelectOne(
|
|
ctx,
|
|
&untilModel,
|
|
`SELECT leasedUntil FROM crlShards WHERE issuerID = ? AND idx = ? LIMIT 1`,
|
|
res.IssuerNameID,
|
|
res.ShardIdx,
|
|
)
|
|
test.AssertNotError(t, err, "getting updated lease timestamp")
|
|
test.Assert(t, untilModel.LeasedUntil.Equal(until), "checking updated lease timestamp")
|
|
|
|
// Leasing a leased shard should fail.
|
|
_, err = sa.leaseSpecificCRLShard(
|
|
context.Background(),
|
|
&sapb.LeaseCRLShardRequest{
|
|
IssuerNameID: 1,
|
|
MinShardIdx: 0,
|
|
MaxShardIdx: 0,
|
|
Until: timestamppb.New(until),
|
|
},
|
|
)
|
|
test.AssertError(t, err, "leasing unavailable shard")
|
|
|
|
// Leasing more than one shard should fail.
|
|
_, err = sa.leaseSpecificCRLShard(
|
|
context.Background(),
|
|
&sapb.LeaseCRLShardRequest{
|
|
IssuerNameID: 1,
|
|
MinShardIdx: 1,
|
|
MaxShardIdx: 2,
|
|
Until: timestamppb.New(until),
|
|
},
|
|
)
|
|
test.AssertError(t, err, "did not lease one specific shard")
|
|
}
|
|
|
|
func TestUpdateCRLShard(t *testing.T) {
|
|
sa, clk, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
// Create 8 shards: 4 for each of 2 issuers. For each issuer, one shard is
|
|
// currently leased, three are available, and one of those failed to update.
|
|
_, err := sa.dbMap.ExecContext(ctx,
|
|
`INSERT INTO crlShards (issuerID, idx, thisUpdate, nextUpdate, leasedUntil) VALUES
|
|
(1, 0, ?, ?, ?),
|
|
(1, 1, ?, ?, ?),
|
|
(1, 2, ?, ?, ?),
|
|
(1, 3, NULL, NULL, ?),
|
|
(2, 0, ?, ?, ?),
|
|
(2, 1, ?, ?, ?),
|
|
(2, 2, ?, ?, ?),
|
|
(2, 3, NULL, NULL, ?);`,
|
|
clk.Now().Add(-7*24*time.Hour), clk.Now().Add(3*24*time.Hour), clk.Now().Add(time.Hour),
|
|
clk.Now().Add(-6*24*time.Hour), clk.Now().Add(4*24*time.Hour), clk.Now().Add(-6*24*time.Hour),
|
|
clk.Now().Add(-5*24*time.Hour), clk.Now().Add(5*24*time.Hour), clk.Now().Add(-5*24*time.Hour),
|
|
clk.Now().Add(-4*24*time.Hour),
|
|
clk.Now().Add(-7*24*time.Hour), clk.Now().Add(3*24*time.Hour), clk.Now().Add(time.Hour),
|
|
clk.Now().Add(-6*24*time.Hour), clk.Now().Add(4*24*time.Hour), clk.Now().Add(-6*24*time.Hour),
|
|
clk.Now().Add(-5*24*time.Hour), clk.Now().Add(5*24*time.Hour), clk.Now().Add(-5*24*time.Hour),
|
|
clk.Now().Add(-4*24*time.Hour),
|
|
)
|
|
test.AssertNotError(t, err, "setting up test shards")
|
|
|
|
thisUpdate := clk.Now().Truncate(time.Second).UTC()
|
|
var crlModel struct {
|
|
ThisUpdate *time.Time
|
|
NextUpdate *time.Time
|
|
}
|
|
|
|
// Updating a leased shard should work.
|
|
_, err = sa.UpdateCRLShard(
|
|
context.Background(),
|
|
&sapb.UpdateCRLShardRequest{
|
|
IssuerNameID: 1,
|
|
ShardIdx: 0,
|
|
ThisUpdate: timestamppb.New(thisUpdate),
|
|
NextUpdate: timestamppb.New(thisUpdate.Add(10 * 24 * time.Hour)),
|
|
},
|
|
)
|
|
test.AssertNotError(t, err, "updating leased shard")
|
|
|
|
err = sa.dbMap.SelectOne(
|
|
ctx,
|
|
&crlModel,
|
|
`SELECT thisUpdate FROM crlShards WHERE issuerID = 1 AND idx = 0 LIMIT 1`,
|
|
)
|
|
test.AssertNotError(t, err, "getting updated thisUpdate timestamp")
|
|
test.Assert(t, crlModel.ThisUpdate.Equal(thisUpdate), "checking updated thisUpdate timestamp")
|
|
|
|
// Updating an unleased shard should work.
|
|
_, err = sa.UpdateCRLShard(
|
|
context.Background(),
|
|
&sapb.UpdateCRLShardRequest{
|
|
IssuerNameID: 1,
|
|
ShardIdx: 1,
|
|
ThisUpdate: timestamppb.New(thisUpdate),
|
|
NextUpdate: timestamppb.New(thisUpdate.Add(10 * 24 * time.Hour)),
|
|
},
|
|
)
|
|
test.AssertNotError(t, err, "updating unleased shard")
|
|
|
|
err = sa.dbMap.SelectOne(
|
|
ctx,
|
|
&crlModel,
|
|
`SELECT thisUpdate FROM crlShards WHERE issuerID = 1 AND idx = 1 LIMIT 1`,
|
|
)
|
|
test.AssertNotError(t, err, "getting updated thisUpdate timestamp")
|
|
test.Assert(t, crlModel.ThisUpdate.Equal(thisUpdate), "checking updated thisUpdate timestamp")
|
|
|
|
// Updating without supplying a NextUpdate should work.
|
|
_, err = sa.UpdateCRLShard(
|
|
context.Background(),
|
|
&sapb.UpdateCRLShardRequest{
|
|
IssuerNameID: 1,
|
|
ShardIdx: 3,
|
|
ThisUpdate: timestamppb.New(thisUpdate.Add(time.Second)),
|
|
},
|
|
)
|
|
test.AssertNotError(t, err, "updating shard without NextUpdate")
|
|
|
|
err = sa.dbMap.SelectOne(
|
|
ctx,
|
|
&crlModel,
|
|
`SELECT nextUpdate FROM crlShards WHERE issuerID = 1 AND idx = 3 LIMIT 1`,
|
|
)
|
|
test.AssertNotError(t, err, "getting updated nextUpdate timestamp")
|
|
test.AssertBoxedNil(t, crlModel.NextUpdate, "checking updated nextUpdate timestamp")
|
|
|
|
// Updating a shard to an earlier time should fail.
|
|
_, err = sa.UpdateCRLShard(
|
|
context.Background(),
|
|
&sapb.UpdateCRLShardRequest{
|
|
IssuerNameID: 1,
|
|
ShardIdx: 1,
|
|
ThisUpdate: timestamppb.New(thisUpdate.Add(-24 * time.Hour)),
|
|
NextUpdate: timestamppb.New(thisUpdate.Add(9 * 24 * time.Hour)),
|
|
},
|
|
)
|
|
test.AssertError(t, err, "updating shard to an earlier time")
|
|
|
|
// Updating an unknown shard should fail.
|
|
_, err = sa.UpdateCRLShard(
|
|
context.Background(),
|
|
&sapb.UpdateCRLShardRequest{
|
|
IssuerNameID: 1,
|
|
ShardIdx: 4,
|
|
ThisUpdate: timestamppb.New(thisUpdate),
|
|
NextUpdate: timestamppb.New(thisUpdate.Add(10 * 24 * time.Hour)),
|
|
},
|
|
)
|
|
test.AssertError(t, err, "updating an unknown shard")
|
|
}
|
|
|
|
func TestReplacementOrderExists(t *testing.T) {
|
|
if os.Getenv("BOULDER_CONFIG_DIR") != "test/config-next" {
|
|
t.Skip("Test requires replacementOrders database table")
|
|
}
|
|
|
|
sa, fc, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
features.Set(features.Config{TrackReplacementCertificatesARI: true})
|
|
defer features.Reset()
|
|
|
|
oldCertSerial := "1234567890"
|
|
|
|
// Check that a non-existent replacement order does not exist.
|
|
exists, err := sa.ReplacementOrderExists(ctx, &sapb.Serial{Serial: oldCertSerial})
|
|
test.AssertNotError(t, err, "failed to check for replacement order")
|
|
test.Assert(t, !exists.Exists, "replacement for non-existent serial should not exist")
|
|
|
|
// Create a test registration to reference.
|
|
reg := createWorkingRegistration(t, sa)
|
|
|
|
// Add one valid authz.
|
|
expires := fc.Now().Add(time.Hour)
|
|
attemptedAt := fc.Now()
|
|
authzID := createFinalizedAuthorization(t, sa, "example.com", expires, "valid", attemptedAt)
|
|
|
|
// Add a new order in pending status with no certificate serial.
|
|
expires1Year := sa.clk.Now().Add(365 * 24 * time.Hour)
|
|
order, err := sa.NewOrderAndAuthzs(ctx, &sapb.NewOrderAndAuthzsRequest{
|
|
NewOrder: &sapb.NewOrderRequest{
|
|
RegistrationID: reg.Id,
|
|
Expires: timestamppb.New(expires1Year),
|
|
DnsNames: []string{"example.com"},
|
|
V2Authorizations: []int64{authzID},
|
|
},
|
|
})
|
|
test.AssertNotError(t, err, "NewOrderAndAuthzs failed")
|
|
|
|
// Set the order to processing so it can be finalized
|
|
_, err = sa.SetOrderProcessing(ctx, &sapb.OrderRequest{Id: order.Id})
|
|
test.AssertNotError(t, err, "SetOrderProcessing failed")
|
|
|
|
// Finalize the order with a certificate oldCertSerial.
|
|
order.CertificateSerial = oldCertSerial
|
|
_, err = sa.FinalizeOrder(ctx, &sapb.FinalizeOrderRequest{Id: order.Id, CertificateSerial: order.CertificateSerial})
|
|
test.AssertNotError(t, err, "FinalizeOrder failed")
|
|
|
|
// Create a replacement order.
|
|
order, err = sa.NewOrderAndAuthzs(ctx, &sapb.NewOrderAndAuthzsRequest{
|
|
NewOrder: &sapb.NewOrderRequest{
|
|
RegistrationID: reg.Id,
|
|
Expires: timestamppb.New(expires1Year),
|
|
DnsNames: []string{"example.com"},
|
|
V2Authorizations: []int64{authzID},
|
|
ReplacesSerial: oldCertSerial,
|
|
},
|
|
})
|
|
test.AssertNotError(t, err, "NewOrderAndAuthzs failed")
|
|
|
|
// Check that a pending replacement order exists.
|
|
exists, err = sa.ReplacementOrderExists(ctx, &sapb.Serial{Serial: oldCertSerial})
|
|
test.AssertNotError(t, err, "failed to check for replacement order")
|
|
test.Assert(t, exists.Exists, "replacement order should exist")
|
|
|
|
// Set the order to processing so it can be finalized.
|
|
_, err = sa.SetOrderProcessing(ctx, &sapb.OrderRequest{Id: order.Id})
|
|
test.AssertNotError(t, err, "SetOrderProcessing failed")
|
|
|
|
// Check that a replacement order in processing still exists.
|
|
exists, err = sa.ReplacementOrderExists(ctx, &sapb.Serial{Serial: oldCertSerial})
|
|
test.AssertNotError(t, err, "failed to check for replacement order")
|
|
test.Assert(t, exists.Exists, "replacement order in processing should still exist")
|
|
|
|
order.CertificateSerial = "0123456789"
|
|
_, err = sa.FinalizeOrder(ctx, &sapb.FinalizeOrderRequest{Id: order.Id, CertificateSerial: order.CertificateSerial})
|
|
test.AssertNotError(t, err, "FinalizeOrder failed")
|
|
|
|
// Check that a finalized replacement order still exists.
|
|
exists, err = sa.ReplacementOrderExists(ctx, &sapb.Serial{Serial: oldCertSerial})
|
|
test.AssertNotError(t, err, "failed to check for replacement order")
|
|
test.Assert(t, exists.Exists, "replacement order in processing should still exist")
|
|
|
|
// Try updating the replacement order.
|
|
|
|
// Create a replacement order.
|
|
newReplacementOrder, err := sa.NewOrderAndAuthzs(ctx, &sapb.NewOrderAndAuthzsRequest{
|
|
NewOrder: &sapb.NewOrderRequest{
|
|
RegistrationID: reg.Id,
|
|
Expires: timestamppb.New(expires1Year),
|
|
DnsNames: []string{"example.com"},
|
|
V2Authorizations: []int64{authzID},
|
|
ReplacesSerial: oldCertSerial,
|
|
},
|
|
})
|
|
test.AssertNotError(t, err, "NewOrderAndAuthzs failed")
|
|
|
|
// Fetch the replacement order so we can ensure it was updated.
|
|
var replacementRow replacementOrderModel
|
|
err = sa.dbReadOnlyMap.SelectOne(
|
|
ctx,
|
|
&replacementRow,
|
|
"SELECT * FROM replacementOrders WHERE serial = ? LIMIT 1",
|
|
oldCertSerial,
|
|
)
|
|
test.AssertNotError(t, err, "SELECT from replacementOrders failed")
|
|
test.AssertEquals(t, newReplacementOrder.Id, replacementRow.OrderID)
|
|
test.AssertEquals(t, newReplacementOrder.Expires.AsTime(), replacementRow.OrderExpires)
|
|
}
|
|
|
|
func TestGetSerialsByKey(t *testing.T) {
|
|
sa, fc, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
// Insert four rows into keyHashToSerial: two that should match the query,
|
|
// one that should not match due to keyHash mismatch, and one that should not
|
|
// match due to being already expired.
|
|
expectedHash := make([]byte, 32)
|
|
expectedHash[0] = 1
|
|
differentHash := make([]byte, 32)
|
|
differentHash[0] = 2
|
|
inserts := []keyHashModel{
|
|
{
|
|
KeyHash: expectedHash,
|
|
CertSerial: "1",
|
|
CertNotAfter: fc.Now().Add(time.Hour),
|
|
},
|
|
{
|
|
KeyHash: expectedHash,
|
|
CertSerial: "2",
|
|
CertNotAfter: fc.Now().Add(2 * time.Hour),
|
|
},
|
|
{
|
|
KeyHash: expectedHash,
|
|
CertSerial: "3",
|
|
CertNotAfter: fc.Now().Add(-1 * time.Hour),
|
|
},
|
|
{
|
|
KeyHash: differentHash,
|
|
CertSerial: "4",
|
|
CertNotAfter: fc.Now().Add(time.Hour),
|
|
},
|
|
}
|
|
|
|
for _, row := range inserts {
|
|
err := sa.dbMap.Insert(context.Background(), &row)
|
|
test.AssertNotError(t, err, "inserting test keyHash")
|
|
}
|
|
|
|
// Expect the result res to have two entries.
|
|
res := make(chan *sapb.Serial)
|
|
stream := &fakeServerStream[sapb.Serial]{output: res}
|
|
var err error
|
|
go func() {
|
|
err = sa.GetSerialsByKey(&sapb.SPKIHash{KeyHash: expectedHash}, stream)
|
|
close(res) // Let our main test thread continue.
|
|
}()
|
|
|
|
var seen []string
|
|
for serial := range res {
|
|
if !slices.Contains([]string{"1", "2"}, serial.Serial) {
|
|
t.Errorf("Received unexpected serial %q", serial.Serial)
|
|
}
|
|
if slices.Contains(seen, serial.Serial) {
|
|
t.Errorf("Received serial %q more than once", serial.Serial)
|
|
}
|
|
seen = append(seen, serial.Serial)
|
|
}
|
|
test.AssertNotError(t, err, "calling GetSerialsByKey")
|
|
test.AssertEquals(t, len(seen), 2)
|
|
}
|
|
|
|
func TestGetSerialsByAccount(t *testing.T) {
|
|
sa, fc, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
expectedReg := createWorkingRegistration(t, sa)
|
|
|
|
// Insert three rows into the serials table: two that should match the query,
|
|
// and one that should not match due to being already expired. We do not here
|
|
// test filtering on the regID itself, because our test setup makes it very
|
|
// hard to insert two fake registrations rows with different IDs.
|
|
inserts := []recordedSerialModel{
|
|
{
|
|
Serial: "1",
|
|
RegistrationID: expectedReg.Id,
|
|
Created: fc.Now().Add(-23 * time.Hour),
|
|
Expires: fc.Now().Add(time.Hour),
|
|
},
|
|
{
|
|
Serial: "2",
|
|
RegistrationID: expectedReg.Id,
|
|
Created: fc.Now().Add(-22 * time.Hour),
|
|
Expires: fc.Now().Add(2 * time.Hour),
|
|
},
|
|
{
|
|
Serial: "3",
|
|
RegistrationID: expectedReg.Id,
|
|
Created: fc.Now().Add(-23 * time.Hour),
|
|
Expires: fc.Now().Add(-1 * time.Hour),
|
|
},
|
|
}
|
|
|
|
for _, row := range inserts {
|
|
err := sa.dbMap.Insert(context.Background(), &row)
|
|
test.AssertNotError(t, err, "inserting test serial")
|
|
}
|
|
|
|
// Expect the result stream to have two entries.
|
|
res := make(chan *sapb.Serial)
|
|
stream := &fakeServerStream[sapb.Serial]{output: res}
|
|
var err error
|
|
go func() {
|
|
err = sa.GetSerialsByAccount(&sapb.RegistrationID{Id: expectedReg.Id}, stream)
|
|
close(res) // Let our main test thread continue.
|
|
}()
|
|
|
|
var seen []string
|
|
for serial := range res {
|
|
if !slices.Contains([]string{"1", "2"}, serial.Serial) {
|
|
t.Errorf("Received unexpected serial %q", serial.Serial)
|
|
}
|
|
if slices.Contains(seen, serial.Serial) {
|
|
t.Errorf("Received serial %q more than once", serial.Serial)
|
|
}
|
|
seen = append(seen, serial.Serial)
|
|
}
|
|
test.AssertNotError(t, err, "calling GetSerialsByAccount")
|
|
test.AssertEquals(t, len(seen), 2)
|
|
}
|
|
|
|
func TestUnpauseAccount(t *testing.T) {
|
|
if os.Getenv("BOULDER_CONFIG_DIR") != "test/config-next" {
|
|
t.Skip("Test requires paused database table")
|
|
}
|
|
sa, _, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
tests := []struct {
|
|
name string
|
|
state []pausedModel
|
|
req *sapb.RegistrationID
|
|
}{
|
|
{
|
|
name: "UnpauseAccount with no paused identifiers",
|
|
state: nil,
|
|
req: &sapb.RegistrationID{Id: 1},
|
|
},
|
|
{
|
|
name: "UnpauseAccount with one paused identifier",
|
|
state: []pausedModel{
|
|
{
|
|
RegistrationID: 1,
|
|
identifierModel: identifierModel{
|
|
Type: identifierTypeToUint[string(identifier.TypeDNS)],
|
|
Value: "example.com",
|
|
},
|
|
PausedAt: sa.clk.Now().Add(-time.Hour),
|
|
},
|
|
},
|
|
req: &sapb.RegistrationID{Id: 1},
|
|
},
|
|
{
|
|
name: "UnpauseAccount with multiple paused identifiers",
|
|
state: []pausedModel{
|
|
{
|
|
RegistrationID: 1,
|
|
identifierModel: identifierModel{
|
|
Type: identifierTypeToUint[string(identifier.TypeDNS)],
|
|
Value: "example.com",
|
|
},
|
|
PausedAt: sa.clk.Now().Add(-time.Hour),
|
|
},
|
|
{
|
|
RegistrationID: 1,
|
|
identifierModel: identifierModel{
|
|
Type: identifierTypeToUint[string(identifier.TypeDNS)],
|
|
Value: "example.net",
|
|
},
|
|
PausedAt: sa.clk.Now().Add(-time.Hour),
|
|
},
|
|
{
|
|
RegistrationID: 1,
|
|
identifierModel: identifierModel{
|
|
Type: identifierTypeToUint[string(identifier.TypeDNS)],
|
|
Value: "example.org",
|
|
},
|
|
PausedAt: sa.clk.Now().Add(-time.Hour),
|
|
},
|
|
},
|
|
req: &sapb.RegistrationID{Id: 1},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
defer func() {
|
|
// Drop all rows from the paused table.
|
|
_, err := sa.dbMap.ExecContext(ctx, "TRUNCATE TABLE paused")
|
|
test.AssertNotError(t, err, "truncating paused table")
|
|
}()
|
|
|
|
// Setup table state.
|
|
for _, state := range tt.state {
|
|
err := sa.dbMap.Insert(ctx, &state)
|
|
test.AssertNotError(t, err, "inserting test identifier")
|
|
}
|
|
|
|
_, err := sa.UnpauseAccount(ctx, tt.req)
|
|
test.AssertNotError(t, err, "Unexpected error for UnpauseAccount()")
|
|
|
|
// Count the number of paused identifiers.
|
|
var count int
|
|
err = sa.dbReadOnlyMap.SelectOne(
|
|
ctx,
|
|
&count,
|
|
"SELECT COUNT(*) FROM paused WHERE registrationID = ? AND unpausedAt IS NULL",
|
|
tt.req.Id,
|
|
)
|
|
test.AssertNotError(t, err, "SELECT COUNT(*) failed")
|
|
test.AssertEquals(t, count, 0)
|
|
})
|
|
}
|
|
}
|
|
|
|
func bulkInsertPausedIdentifiers(ctx context.Context, sa *SQLStorageAuthority, count int) error {
|
|
const batchSize = 1000
|
|
|
|
values := make([]interface{}, 0, batchSize*4)
|
|
now := sa.clk.Now().Add(-time.Hour)
|
|
batches := (count + batchSize - 1) / batchSize
|
|
|
|
for batch := 0; batch < batches; batch++ {
|
|
query := `
|
|
INSERT INTO paused (registrationID, identifierType, identifierValue, pausedAt)
|
|
VALUES`
|
|
|
|
start := batch * batchSize
|
|
end := start + batchSize
|
|
if end > count {
|
|
end = count
|
|
}
|
|
|
|
for i := start; i < end; i++ {
|
|
if i > start {
|
|
query += ","
|
|
}
|
|
query += "(?, ?, ?, ?)"
|
|
values = append(values, 1, identifierTypeToUint[string(identifier.TypeDNS)], fmt.Sprintf("example%d.com", i), now)
|
|
}
|
|
|
|
_, err := sa.dbMap.ExecContext(ctx, query, values...)
|
|
if err != nil {
|
|
return fmt.Errorf("bulk inserting paused identifiers: %w", err)
|
|
}
|
|
values = values[:0]
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func TestUnpauseAccountWithTwoLoops(t *testing.T) {
|
|
if os.Getenv("BOULDER_CONFIG_DIR") != "test/config-next" {
|
|
t.Skip("Test requires paused database table")
|
|
}
|
|
|
|
sa, _, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
err := bulkInsertPausedIdentifiers(ctx, sa, 12000)
|
|
test.AssertNotError(t, err, "bulk inserting paused identifiers")
|
|
|
|
result, err := sa.UnpauseAccount(ctx, &sapb.RegistrationID{Id: 1})
|
|
test.AssertNotError(t, err, "Unexpected error for UnpauseAccount()")
|
|
test.AssertEquals(t, result.Count, int64(12000))
|
|
}
|
|
|
|
func TestUnpauseAccountWithMaxLoops(t *testing.T) {
|
|
if os.Getenv("BOULDER_CONFIG_DIR") != "test/config-next" {
|
|
t.Skip("Test requires paused database table")
|
|
}
|
|
|
|
sa, _, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
err := bulkInsertPausedIdentifiers(ctx, sa, 50001)
|
|
test.AssertNotError(t, err, "bulk inserting paused identifiers")
|
|
|
|
result, err := sa.UnpauseAccount(ctx, &sapb.RegistrationID{Id: 1})
|
|
test.AssertNotError(t, err, "Unexpected error for UnpauseAccount()")
|
|
test.AssertEquals(t, result.Count, int64(50000))
|
|
}
|
|
|
|
func TestPauseIdentifiers(t *testing.T) {
|
|
if os.Getenv("BOULDER_CONFIG_DIR") != "test/config-next" {
|
|
t.Skip("Test requires paused database table")
|
|
}
|
|
sa, _, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
ptrTime := func(t time.Time) *time.Time {
|
|
return &t
|
|
}
|
|
|
|
fourWeeksAgo := sa.clk.Now().Add(-4 * 7 * 24 * time.Hour)
|
|
threeWeeksAgo := sa.clk.Now().Add(-3 * 7 * 24 * time.Hour)
|
|
|
|
tests := []struct {
|
|
name string
|
|
state []pausedModel
|
|
req *sapb.PauseRequest
|
|
want *sapb.PauseIdentifiersResponse
|
|
}{
|
|
{
|
|
name: "An identifier which is not now or previously paused",
|
|
state: nil,
|
|
req: &sapb.PauseRequest{
|
|
RegistrationID: 1,
|
|
Identifiers: []*corepb.Identifier{
|
|
{
|
|
Type: string(identifier.TypeDNS),
|
|
Value: "example.com",
|
|
},
|
|
},
|
|
},
|
|
want: &sapb.PauseIdentifiersResponse{
|
|
Paused: 1,
|
|
Repaused: 0,
|
|
},
|
|
},
|
|
{
|
|
name: "One unpaused entry which was previously paused",
|
|
state: []pausedModel{
|
|
{
|
|
RegistrationID: 1,
|
|
identifierModel: identifierModel{
|
|
Type: identifierTypeToUint[string(identifier.TypeDNS)],
|
|
Value: "example.com",
|
|
},
|
|
PausedAt: fourWeeksAgo,
|
|
UnpausedAt: ptrTime(threeWeeksAgo),
|
|
},
|
|
},
|
|
req: &sapb.PauseRequest{
|
|
RegistrationID: 1,
|
|
Identifiers: []*corepb.Identifier{
|
|
{
|
|
Type: string(identifier.TypeDNS),
|
|
Value: "example.com",
|
|
},
|
|
},
|
|
},
|
|
want: &sapb.PauseIdentifiersResponse{
|
|
Paused: 0,
|
|
Repaused: 1,
|
|
},
|
|
},
|
|
{
|
|
name: "One unpaused entry which was previously paused and unpaused less than 2 weeks ago",
|
|
state: []pausedModel{
|
|
{
|
|
RegistrationID: 1,
|
|
identifierModel: identifierModel{
|
|
Type: identifierTypeToUint[string(identifier.TypeDNS)],
|
|
Value: "example.com",
|
|
},
|
|
PausedAt: fourWeeksAgo,
|
|
UnpausedAt: ptrTime(sa.clk.Now().Add(-13 * 24 * time.Hour)),
|
|
},
|
|
},
|
|
req: &sapb.PauseRequest{
|
|
RegistrationID: 1,
|
|
Identifiers: []*corepb.Identifier{
|
|
{
|
|
Type: string(identifier.TypeDNS),
|
|
Value: "example.com",
|
|
},
|
|
},
|
|
},
|
|
want: &sapb.PauseIdentifiersResponse{
|
|
Paused: 0,
|
|
Repaused: 0,
|
|
},
|
|
},
|
|
{
|
|
name: "An identifier which is currently paused",
|
|
state: []pausedModel{
|
|
{
|
|
RegistrationID: 1,
|
|
identifierModel: identifierModel{
|
|
Type: identifierTypeToUint[string(identifier.TypeDNS)],
|
|
Value: "example.com",
|
|
},
|
|
PausedAt: fourWeeksAgo,
|
|
},
|
|
},
|
|
req: &sapb.PauseRequest{
|
|
RegistrationID: 1,
|
|
Identifiers: []*corepb.Identifier{
|
|
{
|
|
Type: string(identifier.TypeDNS),
|
|
Value: "example.com",
|
|
},
|
|
},
|
|
},
|
|
want: &sapb.PauseIdentifiersResponse{
|
|
Paused: 0,
|
|
Repaused: 0,
|
|
},
|
|
},
|
|
{
|
|
name: "Two previously paused entries and one new entry",
|
|
state: []pausedModel{
|
|
{
|
|
RegistrationID: 1,
|
|
identifierModel: identifierModel{
|
|
Type: identifierTypeToUint[string(identifier.TypeDNS)],
|
|
Value: "example.com",
|
|
},
|
|
PausedAt: fourWeeksAgo,
|
|
UnpausedAt: ptrTime(threeWeeksAgo),
|
|
},
|
|
{
|
|
RegistrationID: 1,
|
|
identifierModel: identifierModel{
|
|
Type: identifierTypeToUint[string(identifier.TypeDNS)],
|
|
Value: "example.net",
|
|
},
|
|
PausedAt: fourWeeksAgo,
|
|
UnpausedAt: ptrTime(threeWeeksAgo),
|
|
},
|
|
},
|
|
req: &sapb.PauseRequest{
|
|
RegistrationID: 1,
|
|
Identifiers: []*corepb.Identifier{
|
|
{
|
|
Type: string(identifier.TypeDNS),
|
|
Value: "example.com",
|
|
},
|
|
{
|
|
Type: string(identifier.TypeDNS),
|
|
Value: "example.net",
|
|
},
|
|
{
|
|
Type: string(identifier.TypeDNS),
|
|
Value: "example.org",
|
|
},
|
|
},
|
|
},
|
|
want: &sapb.PauseIdentifiersResponse{
|
|
Paused: 1,
|
|
Repaused: 2,
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
defer func() {
|
|
// Drop all rows from the paused table.
|
|
_, err := sa.dbMap.ExecContext(ctx, "TRUNCATE TABLE paused")
|
|
test.AssertNotError(t, err, "Truncate table paused failed")
|
|
}()
|
|
|
|
// Setup table state.
|
|
for _, state := range tt.state {
|
|
err := sa.dbMap.Insert(ctx, &state)
|
|
test.AssertNotError(t, err, "inserting test identifier")
|
|
}
|
|
|
|
got, err := sa.PauseIdentifiers(ctx, tt.req)
|
|
test.AssertNotError(t, err, "Unexpected error for PauseIdentifiers()")
|
|
test.AssertEquals(t, got.Paused, tt.want.Paused)
|
|
test.AssertEquals(t, got.Repaused, tt.want.Repaused)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCheckIdentifiersPaused(t *testing.T) {
|
|
if os.Getenv("BOULDER_CONFIG_DIR") != "test/config-next" {
|
|
t.Skip("Test requires paused database table")
|
|
}
|
|
sa, _, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
ptrTime := func(t time.Time) *time.Time {
|
|
return &t
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
state []pausedModel
|
|
req *sapb.PauseRequest
|
|
want *sapb.Identifiers
|
|
}{
|
|
{
|
|
name: "No paused identifiers",
|
|
state: nil,
|
|
req: &sapb.PauseRequest{
|
|
RegistrationID: 1,
|
|
Identifiers: []*corepb.Identifier{
|
|
{
|
|
Type: string(identifier.TypeDNS),
|
|
Value: "example.com",
|
|
},
|
|
},
|
|
},
|
|
want: &sapb.Identifiers{
|
|
Identifiers: []*corepb.Identifier{},
|
|
},
|
|
},
|
|
{
|
|
name: "One paused identifier",
|
|
state: []pausedModel{
|
|
{
|
|
RegistrationID: 1,
|
|
identifierModel: identifierModel{
|
|
Type: identifierTypeToUint[string(identifier.TypeDNS)],
|
|
Value: "example.com",
|
|
},
|
|
PausedAt: sa.clk.Now().Add(-time.Hour),
|
|
},
|
|
},
|
|
req: &sapb.PauseRequest{
|
|
RegistrationID: 1,
|
|
Identifiers: []*corepb.Identifier{
|
|
{
|
|
Type: string(identifier.TypeDNS),
|
|
Value: "example.com",
|
|
},
|
|
},
|
|
},
|
|
want: &sapb.Identifiers{
|
|
Identifiers: []*corepb.Identifier{
|
|
{
|
|
Type: string(identifier.TypeDNS),
|
|
Value: "example.com",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Two paused identifiers, one unpaused",
|
|
state: []pausedModel{
|
|
{
|
|
RegistrationID: 1,
|
|
identifierModel: identifierModel{
|
|
Type: identifierTypeToUint[string(identifier.TypeDNS)],
|
|
Value: "example.com",
|
|
},
|
|
PausedAt: sa.clk.Now().Add(-time.Hour),
|
|
},
|
|
{
|
|
RegistrationID: 1,
|
|
identifierModel: identifierModel{
|
|
Type: identifierTypeToUint[string(identifier.TypeDNS)],
|
|
Value: "example.net",
|
|
},
|
|
PausedAt: sa.clk.Now().Add(-time.Hour),
|
|
},
|
|
{
|
|
RegistrationID: 1,
|
|
identifierModel: identifierModel{
|
|
Type: identifierTypeToUint[string(identifier.TypeDNS)],
|
|
Value: "example.org",
|
|
},
|
|
PausedAt: sa.clk.Now().Add(-time.Hour),
|
|
UnpausedAt: ptrTime(sa.clk.Now().Add(-time.Minute)),
|
|
},
|
|
},
|
|
req: &sapb.PauseRequest{
|
|
RegistrationID: 1,
|
|
Identifiers: []*corepb.Identifier{
|
|
{
|
|
Type: string(identifier.TypeDNS),
|
|
Value: "example.com",
|
|
},
|
|
{
|
|
Type: string(identifier.TypeDNS),
|
|
Value: "example.net",
|
|
},
|
|
{
|
|
Type: string(identifier.TypeDNS),
|
|
Value: "example.org",
|
|
},
|
|
},
|
|
},
|
|
want: &sapb.Identifiers{
|
|
Identifiers: []*corepb.Identifier{
|
|
{
|
|
Type: string(identifier.TypeDNS),
|
|
Value: "example.com",
|
|
},
|
|
{
|
|
Type: string(identifier.TypeDNS),
|
|
Value: "example.net",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
defer func() {
|
|
// Drop all rows from the paused table.
|
|
_, err := sa.dbMap.ExecContext(ctx, "TRUNCATE TABLE paused")
|
|
test.AssertNotError(t, err, "Truncate table paused failed")
|
|
}()
|
|
|
|
// Setup table state.
|
|
for _, state := range tt.state {
|
|
err := sa.dbMap.Insert(ctx, &state)
|
|
test.AssertNotError(t, err, "inserting test identifier")
|
|
}
|
|
|
|
got, err := sa.CheckIdentifiersPaused(ctx, tt.req)
|
|
test.AssertNotError(t, err, "Unexpected error for PauseIdentifiers()")
|
|
test.AssertDeepEquals(t, got.Identifiers, tt.want.Identifiers)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetPausedIdentifiers(t *testing.T) {
|
|
if os.Getenv("BOULDER_CONFIG_DIR") != "test/config-next" {
|
|
t.Skip("Test requires paused database table")
|
|
}
|
|
sa, _, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
ptrTime := func(t time.Time) *time.Time {
|
|
return &t
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
state []pausedModel
|
|
req *sapb.RegistrationID
|
|
want *sapb.Identifiers
|
|
}{
|
|
{
|
|
name: "No paused identifiers",
|
|
state: nil,
|
|
req: &sapb.RegistrationID{Id: 1},
|
|
want: &sapb.Identifiers{
|
|
Identifiers: []*corepb.Identifier{},
|
|
},
|
|
},
|
|
{
|
|
name: "One paused identifier",
|
|
state: []pausedModel{
|
|
{
|
|
RegistrationID: 1,
|
|
identifierModel: identifierModel{
|
|
Type: identifierTypeToUint[string(identifier.TypeDNS)],
|
|
Value: "example.com",
|
|
},
|
|
PausedAt: sa.clk.Now().Add(-time.Hour),
|
|
},
|
|
},
|
|
req: &sapb.RegistrationID{Id: 1},
|
|
want: &sapb.Identifiers{
|
|
Identifiers: []*corepb.Identifier{
|
|
{
|
|
Type: string(identifier.TypeDNS),
|
|
Value: "example.com",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Two paused identifiers, one unpaused",
|
|
state: []pausedModel{
|
|
{
|
|
RegistrationID: 1,
|
|
identifierModel: identifierModel{
|
|
Type: identifierTypeToUint[string(identifier.TypeDNS)],
|
|
Value: "example.com",
|
|
},
|
|
PausedAt: sa.clk.Now().Add(-time.Hour),
|
|
},
|
|
{
|
|
RegistrationID: 1,
|
|
identifierModel: identifierModel{
|
|
Type: identifierTypeToUint[string(identifier.TypeDNS)],
|
|
Value: "example.net",
|
|
},
|
|
PausedAt: sa.clk.Now().Add(-time.Hour),
|
|
},
|
|
{
|
|
RegistrationID: 1,
|
|
identifierModel: identifierModel{
|
|
Type: identifierTypeToUint[string(identifier.TypeDNS)],
|
|
Value: "example.org",
|
|
},
|
|
PausedAt: sa.clk.Now().Add(-time.Hour),
|
|
UnpausedAt: ptrTime(sa.clk.Now().Add(-time.Minute)),
|
|
},
|
|
},
|
|
req: &sapb.RegistrationID{Id: 1},
|
|
want: &sapb.Identifiers{
|
|
Identifiers: []*corepb.Identifier{
|
|
{
|
|
Type: string(identifier.TypeDNS),
|
|
Value: "example.com",
|
|
},
|
|
{
|
|
Type: string(identifier.TypeDNS),
|
|
Value: "example.net",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
defer func() {
|
|
// Drop all rows from the paused table.
|
|
_, err := sa.dbMap.ExecContext(ctx, "TRUNCATE TABLE paused")
|
|
test.AssertNotError(t, err, "Truncate table paused failed")
|
|
}()
|
|
|
|
// Setup table state.
|
|
for _, state := range tt.state {
|
|
err := sa.dbMap.Insert(ctx, &state)
|
|
test.AssertNotError(t, err, "inserting test identifier")
|
|
}
|
|
|
|
got, err := sa.GetPausedIdentifiers(ctx, tt.req)
|
|
test.AssertNotError(t, err, "Unexpected error for PauseIdentifiers()")
|
|
test.AssertDeepEquals(t, got.Identifiers, tt.want.Identifiers)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetPausedIdentifiersOnlyUnpausesOneAccount(t *testing.T) {
|
|
if os.Getenv("BOULDER_CONFIG_DIR") != "test/config-next" {
|
|
t.Skip("Test requires paused database table")
|
|
}
|
|
sa, _, cleanUp := initSA(t)
|
|
defer cleanUp()
|
|
|
|
// Insert two paused identifiers for two different accounts.
|
|
err := sa.dbMap.Insert(ctx, &pausedModel{
|
|
RegistrationID: 1,
|
|
identifierModel: identifierModel{
|
|
Type: identifierTypeToUint[string(identifier.TypeDNS)],
|
|
Value: "example.com",
|
|
},
|
|
PausedAt: sa.clk.Now().Add(-time.Hour),
|
|
})
|
|
test.AssertNotError(t, err, "inserting test identifier")
|
|
|
|
err = sa.dbMap.Insert(ctx, &pausedModel{
|
|
RegistrationID: 2,
|
|
identifierModel: identifierModel{
|
|
Type: identifierTypeToUint[string(identifier.TypeDNS)],
|
|
Value: "example.net",
|
|
},
|
|
PausedAt: sa.clk.Now().Add(-time.Hour),
|
|
})
|
|
test.AssertNotError(t, err, "inserting test identifier")
|
|
|
|
// Unpause the first account.
|
|
_, err = sa.UnpauseAccount(ctx, &sapb.RegistrationID{Id: 1})
|
|
test.AssertNotError(t, err, "UnpauseAccount failed")
|
|
|
|
// Check that the second account's identifier is still paused.
|
|
identifiers, err := sa.GetPausedIdentifiers(ctx, &sapb.RegistrationID{Id: 2})
|
|
test.AssertNotError(t, err, "GetPausedIdentifiers failed")
|
|
test.AssertEquals(t, len(identifiers.Identifiers), 1)
|
|
test.AssertEquals(t, identifiers.Identifiers[0].Value, "example.net")
|
|
}
|