Compare commits

...

29 Commits

Author SHA1 Message Date
Shiloh Heurich 473b4059c4
feat: Add core definitions for dns-account-01 (#8140)
## Summary

This PR introduces the foundational components required to support
the `dns-account-01` challenge type, as specified in draft-ietf-acme-dns-account-label-00:
https://datatracker.ietf.org/doc/draft-ietf-acme-dns-account-label/.

It focuses only on core definitions and SA support. PA/VA/RA logic will be in
a follow-up change.

Core Definitions & Logic:
- //core/objects.go: Added `ChallengeTypeDNSAccount01` constant and
  updated validation methods
- //core/challenges.go: Added `DNSAccountChallenge01` constructor
  and factory support

Storage Authority (SA) Support:
- //sa/model.go: Added `dns-account-01` to challenge type mappings

Testing:
- //core/*_test.go: Basic definition and validation tests
- //sa/sa_test.go: Database round-trip tests for `dns-account-01`
  challenges

Dependencies:
- Updated github.com/eggsampler/acme/v3 to release version v3.6.2
2025-07-29 09:27:04 -07:00
Aaron Gable 440c6957f9
CA: Truncate notBefore and notAfter to second-level precision (#8319)
When generating the validity period of a to-be-issued certificate,
truncate the notBefore timestamp to second-level precision, trimming off
any nanoseconds which won't be represented in the final certificate. Do
the same for the notAfter, although this should be a no-op since only
whole numbers of seconds are used to compute it from the notBefore.

It's possible that this could cause some of the maxBackdate calculations
to fail, because truncation can cause the notBefore timestamp to move up
to (nearly) 1 second earlier. However, this only becomes a concern in
practice if maxBackdate is set to 10 seconds or less.

This results in cleaner logs, since Go only prints the fractional
seconds portion of a timestamp if it is non-zero:
https://go.dev/play/p/iAeSX3VMrJD

Fixes https://github.com/letsencrypt/boulder/issues/8318
2025-07-28 15:09:55 -07:00
Samantha Frank 80c75ab435
docker: Update CI mariadb from 10.6.22 to 10.11.13 (#8321)
Closes #8307
2025-07-28 17:48:23 -04:00
Jacob Hoffman-Andrews 85d1e3cf5e
sa: use internal fqdnSet model instead of core.FQDNSet (#8314)
Fixes https://github.com/letsencrypt/boulder/issues/8112
2025-07-23 16:49:04 -07:00
James Renken 04ae9ebcda
bad-key-revoker: Add delay to mitigate race condition (#8301)
Add a `MaxExpectedReplicationLag` parameter to `bad-key-revoker`. Wait
that interval before searching for certificates to revoke.

The interval is set to only 100ms in both `test/config` and
`test/config-next` so that integration tests don't require long sleeps.
The default value within BKR is, and the production value should be,
higher.

Part of #5686
2025-07-21 14:18:19 -07:00
dependabot[bot] a3c1e62049
build(deps): bump github.com/redis/go-redis/v9 from 9.7.3 to 9.10.0 (#8313)
Bumps github.com/redis/go-redis/v9 from 9.7.3 to 9.10.0

Commits: https://github.com/redis/go-redis/compare/v9.7.3...v9.10.0
Latest changelog: https://github.com/redis/go-redis/releases/tag/v9.10.0
2025-07-18 15:17:22 -07:00
Aaron Gable cd59eed63d
Ceremony: use pre-existing SKID during cross-signing (#8311)
When cross-signing a pre-existing root, the cross-sign's Subject Key
Identifier field needs to exactly match the existing cert's Subject Key
Identifier. Rather than recompute it, copy it directly from the
"to-be-cross-signed" cert.
2025-07-18 13:08:32 -07:00
Aaron Gable 5a5ae229a0
Ceremony: allow shortening of Subject Organization Name (#8310)
In general, the ceremony tool requires that any Unrestricted Cross Sign
(see Baseline Requirements, Section 7.1.2.2.3) must have a Subject
Organization Name which is identical to the issuer's Organization Name.
Allow a special case whereby a cert (such as ISRG Root X1) which has
Subject Organization Name "Internet Security Research Group" can
cross-certify a cert (such as the upcoming Root YR) which has the
shorter string "ISRG" for that same field.

---

> [!WARNING]
> ~~Do not merge before
https://github.com/letsencrypt/boulder/pull/8309~~
2025-07-17 17:36:05 -07:00
Aaron Gable d5bb88b975
Ceremony: remove support for delegated CRL and OCSP signers (#8309)
Delegated CRL Signers are forbidden by the Baseline Requirements, and we
haven't used Delegated OCSP Responders since 2020. This code is dead,
and creates unnecessary complexity, so remove it.

At the same time, improve our README to reflect these changes and
resolve several formatting lint warnings.
2025-07-17 16:28:26 -07:00
Aaron Gable b9dbcdbba2
Dependabot: add a 30-day cooldown between go dependency updates (#8312)
Documentation for the "cooldown" config parameter is here:
https://docs.github.com/en/code-security/dependabot/working-with-dependabot/dependabot-options-reference#cooldown-
2025-07-17 14:57:58 -07:00
dependabot[bot] 15824538c1
build(deps): bump github.com/aws/aws-sdk-go-v2/service/s3 from 1.82.0 to 1.83.0 in the aws group (#8300)
Updates `github.com/aws/aws-sdk-go-v2/service/s3` from 1.82.0 to 1.83.0

Changelog: https://github.com/aws/aws-sdk-go-v2/compare/service/s3/v1.82.0...service/s3/v1.83.0
2025-07-14 16:45:56 -07:00
Aaron Gable cfd5c12f6f
Add hotfix tag functionality to release tool (#8290)
If given the name of a pre-existing release branch (created with
//tools/release/branch), it will:
- fetch all commits on that branch;
- compute the next release tag by incrementing the highest extant patch
version; and
- create (and optionally push) the new tag.

Most of the logic is contained in the new `nextTagOnBranch` function.
The rest of the diff is slight rearrangement to accommodate that new
function elegantly, and minor cleanups of the branch tool that I found
in the process of writing this change.
2025-07-14 10:10:28 -07:00
James Renken 7e04a7176f
test: Update cert-checker & ca configs to match prod (#8303) 2025-07-10 12:50:30 -07:00
Jacob Hoffman-Andrews 9b3fc4016b
integration: test that IPs in CN are rejected (#8299)
Followup to #8276 and #8282
2025-07-10 11:00:34 -07:00
James Renken 6ba4207153
Add IP prefix blocklist (#8278)
Fixes #8237
2025-07-09 11:28:17 -07:00
Samantha Frank 99be9909a6
test: Remove mail test srv (#8304)
Part of #8199
2025-07-09 11:03:41 -07:00
James Renken 79a7cb3c1b
bad-key-revoker, cmd: Remove unused mailer & SMTPConfig stanzas (#8302)
Part of #8199
2025-07-09 10:33:46 -07:00
James Renken 102997da4a
sa: Drop LockCol from registrations table in db-next (#8298)
`LockCol` in the `registrations` table is no longer used or needed, as
of #8296.

Part of #7934
2025-07-09 09:38:13 -07:00
James Renken 21d304812e
test: Allow IP address issuance in config (#8295)
Issuance for IP address identifiers is now enabled in production.

Fixes #8235
2025-07-08 14:25:34 -07:00
Samantha Frank caa980f9ee
sa: Change a return type for GetEnabledRateLimitOverrides (#8280)
These methods have no callers.

Part of https://github.com/letsencrypt/boulder/issues/8165
2025-07-08 13:59:54 -04:00
James Renken e15368c205
Ignore LockCol in registrations table (#8296)
We stopped actively using `LockCol` in the `registrations` table in
#7911, and `DEFAULT`ed inserts to 0 in #7935.

Part of #7934
2025-07-07 22:07:57 -07:00
Samantha Frank a3eb6aa043
email-exporter: Use the upsert-by-email endpoint (#8297)
Prevent duplicate contacts in Pardot by using the upsert-by-email
endpoint.

Some background: In
https://github.com/letsencrypt/boulder/pull/7998#discussion_r1949702280
(later superseded by https://github.com/letsencrypt/boulder/pull/8016),
we discussed using this endpoint. It turns out I was wrong about Pardot
storing Prospects by email address; you can have multiple Prospects with
the same email. While subscribed newsletters won’t be sent to both
contacts, the onboarding process doesn’t deduplicate in this way and
will send to an email address each time it’s added, resulting in
duplicate onboarding messages.
2025-07-07 16:55:30 -07:00
Samantha Frank 05e631593e
ratelimits: Supporting additions for admin tooling (#8279)
- Export `ValidateLimit()` for use in the admin tool.
- Add utility functions `DumpOverrides()` and
`LoadOverridesByBucketKey()` to dump/load overrides to/from a YAML file.
- Export `Limit` and several of its fields to support calls to
`LoadOverridesByBucketKey()` and `ValidateLimit()`, and to return
results from `DumpOverrides()`.
- Add `BuildBucketKey()`, which builds and validates bucket keys based
on the limit name and provided components.
- Also add a `MarshalYAML()` method to `config.Duration`.

Part of https://github.com/letsencrypt/boulder/issues/8165
2025-07-07 17:01:05 -04:00
James Renken c1ce0c83d0
identifier, policy, va: Remove/reject scope zone from IPv6 addresses (#8294)
Followup to #8293
Fixes #8292
2025-07-07 13:57:21 -07:00
dependabot[bot] 5b380adb53
build(deps): bump github.com/aws/aws-sdk-go-v2/service/s3 from 1.81.0 to 1.82.0 in the aws group (#8285)
Updates `github.com/aws/aws-sdk-go-v2/service/s3` from 1.81.0 to 1.82.0

Changelog: https://github.com/aws/aws-sdk-go-v2/compare/service/s3/v1.81.0...service/s3/v1.82.0
2025-07-07 12:06:03 -07:00
Youfu Zhang bf0439bc12
Fix IsReservedAddr to handle IPv6 zone, add/verify tests (#8293)
- Fixes a bug where IsReservedAddr did not properly handle IPv6
addresses with a zone (scope), due to `netip.Prefix.Contains` returning
false for zoned addresses.
- Now strips the zone before checking.
- Added tests covering IPv6 addresses with zones.

Part of https://github.com/letsencrypt/boulder/pull/8292
2025-07-07 10:46:48 -07:00
Aaron Gable 9d5512abb8
Add AddressesResolved to tls-alpn-01 IP validation records (#8288)
Set the ValidationRecord.AddressesResolved field when performing
tls-alpn-01 validation on an IP address. This field is redundant with
ValidationRecord.Hostname, because we don't perform any resolution of
DNS names to IP addresses when conducting validation of an IP address.
But populating it anyway appeases the safety checks within the
Challenge.RecordsSane() method, and matches the fact that we populate it
for http-01 validation of IP addresses.

Add a check to several unit tests which fails on main but passes with
this change applied.

Fixes https://github.com/letsencrypt/boulder/issues/8286
2025-07-04 22:18:48 -07:00
Aaron Gable d7e4ed18ad
Remove the last vestiges of account contacts (#8247)
Remove the concept of an account contact from all of boulder except the
WFE. Specifically:
- Delete the UpdateRegistrationContact methods from the RA and SA
- Remove all understanding of the Contact field from the RA and SA's
NewRegistration methods
- Remove the Contact field from corepb.Registration to ensure it can
never be communicated anywhere

Note that this does not remove the Contact field from core.Registration,
as that represents an ACME API type, and we're still willing to parse
the Contact field from incoming new-account requests.

Fixes https://github.com/letsencrypt/boulder/issues/8199
2025-07-03 12:55:16 -07:00
James Renken 4d0ae36512
errors: Fix anchor link to rate limits page (#8291) 2025-07-03 11:02:44 -07:00
155 changed files with 5191 additions and 3566 deletions

View File

@ -14,6 +14,8 @@ updates:
schedule:
interval: "weekly"
day: "wednesday"
cooldown:
default-days: 30
- package-ecosystem: "github-actions"
directory: "/"
schedule:

View File

@ -150,8 +150,8 @@ func setup(t *testing.T) *testCtx {
pa, err := policy.New(map[identifier.IdentifierType]bool{"dns": true}, nil, blog.NewMock())
test.AssertNotError(t, err, "Couldn't create PA")
err = pa.LoadHostnamePolicyFile("../test/hostname-policy.yaml")
test.AssertNotError(t, err, "Couldn't set hostname policy")
err = pa.LoadIdentPolicyFile("../test/ident-policy.yaml")
test.AssertNotError(t, err, "Couldn't set identifier policy")
certProfiles := make(map[string]*issuance.ProfileConfig, 0)
certProfiles["legacy"] = &issuance.ProfileConfig{

View File

@ -46,16 +46,17 @@ type revoker interface {
}
type badKeyRevoker struct {
dbMap *db.WrappedMap
maxRevocations int
serialBatchSize int
raClient revoker
logger blog.Logger
clk clock.Clock
backoffIntervalBase time.Duration
backoffIntervalMax time.Duration
backoffFactor float64
backoffTicker int
dbMap *db.WrappedMap
maxRevocations int
serialBatchSize int
raClient revoker
logger blog.Logger
clk clock.Clock
backoffIntervalBase time.Duration
backoffIntervalMax time.Duration
backoffFactor float64
backoffTicker int
maxExpectedReplicationLag time.Duration
}
// uncheckedBlockedKey represents a row in the blockedKeys table
@ -76,8 +77,10 @@ func (bkr *badKeyRevoker) countUncheckedKeys(ctx context.Context) (int, error) {
&count,
`SELECT COUNT(*)
FROM (SELECT 1 FROM blockedKeys
WHERE extantCertificatesChecked = false
WHERE extantCertificatesChecked = false AND added < ? - INTERVAL ? SECOND
LIMIT ?) AS a`,
bkr.clk.Now(),
bkr.maxExpectedReplicationLag.Seconds(),
blockedKeysGaugeLimit,
)
return count, err
@ -90,8 +93,10 @@ func (bkr *badKeyRevoker) selectUncheckedKey(ctx context.Context) (uncheckedBloc
&row,
`SELECT keyHash, revokedBy
FROM blockedKeys
WHERE extantCertificatesChecked = false
WHERE extantCertificatesChecked = false AND added < ? - INTERVAL ? SECOND
LIMIT 1`,
bkr.clk.Now(),
bkr.maxExpectedReplicationLag.Seconds(),
)
return row, err
}
@ -275,6 +280,7 @@ type Config struct {
// is higher than MaximumRevocations bad-key-revoker will error out and refuse to
// progress until this is addressed.
MaximumRevocations int `validate:"gte=0"`
// FindCertificatesBatchSize specifies the maximum number of serials to select from the
// keyHashToSerial table at once
FindCertificatesBatchSize int `validate:"required"`
@ -289,15 +295,12 @@ type Config struct {
// or no work to do.
BackoffIntervalMax config.Duration `validate:"-"`
// Deprecated: the bad-key-revoker no longer sends emails; we use ARI.
// TODO(#8199): Remove this config stanza entirely.
Mailer struct {
cmd.SMTPConfig `validate:"-"`
SMTPTrustedRootFile string
From string
EmailSubject string
EmailTemplate string
}
// MaxExpectedReplicationLag specifies the minimum duration
// bad-key-revoker should wait before searching for certificates
// matching a blockedKeys row. This should be just slightly greater than
// the database's maximum replication lag, and always well under 24
// hours.
MaxExpectedReplicationLag config.Duration `validate:"-"`
}
Syslog cmd.SyslogConfig
@ -340,15 +343,16 @@ func main() {
rac := rapb.NewRegistrationAuthorityClient(conn)
bkr := &badKeyRevoker{
dbMap: dbMap,
maxRevocations: config.BadKeyRevoker.MaximumRevocations,
serialBatchSize: config.BadKeyRevoker.FindCertificatesBatchSize,
raClient: rac,
logger: logger,
clk: clk,
backoffIntervalMax: config.BadKeyRevoker.BackoffIntervalMax.Duration,
backoffIntervalBase: config.BadKeyRevoker.Interval.Duration,
backoffFactor: 1.3,
dbMap: dbMap,
maxRevocations: config.BadKeyRevoker.MaximumRevocations,
serialBatchSize: config.BadKeyRevoker.FindCertificatesBatchSize,
raClient: rac,
logger: logger,
clk: clk,
backoffIntervalMax: config.BadKeyRevoker.BackoffIntervalMax.Duration,
backoffIntervalBase: config.BadKeyRevoker.Interval.Duration,
backoffFactor: 1.3,
maxExpectedReplicationLag: config.BadKeyRevoker.MaxExpectedReplicationLag.Duration,
}
// If `BackoffIntervalMax` was not set via the config, set it to 60
@ -364,6 +368,14 @@ func main() {
bkr.backoffIntervalBase = time.Second
}
// If `MaxExpectedReplicationLag` was not set via the config, then set
// `bkr.maxExpectedReplicationLag` to a default 22 seconds. This is based on
// ProxySQL's max_replication_lag for bad-key-revoker (10s), times two, plus
// two seconds.
if bkr.maxExpectedReplicationLag == 0 {
bkr.maxExpectedReplicationLag = time.Second * 22
}
// Run bad-key-revoker in a loop. Backoff if no work or errors.
for {
noWork, err := bkr.invoke(context.Background())

View File

@ -45,6 +45,12 @@ func insertBlockedRow(t *testing.T, dbMap *db.WrappedMap, fc clock.Clock, hash [
test.AssertNotError(t, err, "failed to add test row")
}
func fcBeforeRepLag(clk clock.Clock, bkr *badKeyRevoker) clock.FakeClock {
fc := clock.NewFake()
fc.Set(clk.Now().Add(-bkr.maxExpectedReplicationLag - time.Second))
return fc
}
func TestSelectUncheckedRows(t *testing.T) {
ctx := context.Background()
@ -55,12 +61,15 @@ func TestSelectUncheckedRows(t *testing.T) {
fc := clock.NewFake()
bkr := &badKeyRevoker{
dbMap: dbMap,
logger: blog.NewMock(),
clk: fc,
dbMap: dbMap,
logger: blog.NewMock(),
clk: fc,
maxExpectedReplicationLag: time.Second * 22,
}
hashA, hashB, hashC := randHash(t), randHash(t), randHash(t)
// insert a blocked key that's marked as already checked
insertBlockedRow(t, dbMap, fc, hashA, 1, true)
count, err := bkr.countUncheckedKeys(ctx)
test.AssertNotError(t, err, "countUncheckedKeys failed")
@ -68,11 +77,14 @@ func TestSelectUncheckedRows(t *testing.T) {
_, err = bkr.selectUncheckedKey(ctx)
test.AssertError(t, err, "selectUncheckedKey didn't fail with no rows to process")
test.Assert(t, db.IsNoRows(err), "returned error is not sql.ErrNoRows")
insertBlockedRow(t, dbMap, fc, hashB, 1, false)
// insert a blocked key that's due to be checked
insertBlockedRow(t, dbMap, fcBeforeRepLag(fc, bkr), hashB, 1, false)
// insert a freshly blocked key, so it's not yet due to be checked
insertBlockedRow(t, dbMap, fc, hashC, 1, false)
count, err = bkr.countUncheckedKeys(ctx)
test.AssertNotError(t, err, "countUncheckedKeys failed")
test.AssertEquals(t, count, 2)
test.AssertEquals(t, count, 1)
row, err := bkr.selectUncheckedKey(ctx)
test.AssertNotError(t, err, "selectUncheckKey failed")
test.AssertByteEquals(t, row.KeyHash, hashB)
@ -86,13 +98,12 @@ func insertRegistration(t *testing.T, dbMap *db.WrappedMap, fc clock.Clock) int6
test.AssertNotError(t, err, "failed to read rand")
res, err := dbMap.ExecContext(
context.Background(),
"INSERT INTO registrations (jwk, jwk_sha256, agreement, createdAt, status, LockCol) VALUES (?, ?, ?, ?, ?, ?)",
"INSERT INTO registrations (jwk, jwk_sha256, agreement, createdAt, status) VALUES (?, ?, ?, ?, ?)",
[]byte{},
fmt.Sprintf("%x", jwkHash),
"yes",
fc.Now(),
string(core.StatusValid),
0,
)
test.AssertNotError(t, err, "failed to insert test registrations row")
regID, err := res.LastInsertId()
@ -192,7 +203,13 @@ func TestFindUnrevokedNoRows(t *testing.T) {
)
test.AssertNotError(t, err, "failed to insert test keyHashToSerial row")
bkr := &badKeyRevoker{dbMap: dbMap, serialBatchSize: 1, maxRevocations: 10, clk: fc}
bkr := &badKeyRevoker{
dbMap: dbMap,
serialBatchSize: 1,
maxRevocations: 10,
clk: fc,
maxExpectedReplicationLag: time.Second * 22,
}
_, err = bkr.findUnrevoked(ctx, uncheckedBlockedKey{KeyHash: hashA})
test.Assert(t, db.IsNoRows(err), "expected NoRows error")
}
@ -208,7 +225,13 @@ func TestFindUnrevoked(t *testing.T) {
regID := insertRegistration(t, dbMap, fc)
bkr := &badKeyRevoker{dbMap: dbMap, serialBatchSize: 1, maxRevocations: 10, clk: fc}
bkr := &badKeyRevoker{
dbMap: dbMap,
serialBatchSize: 1,
maxRevocations: 10,
clk: fc,
maxExpectedReplicationLag: time.Second * 22,
}
hashA := randHash(t)
// insert valid, unexpired
@ -252,7 +275,11 @@ func TestRevokeCerts(t *testing.T) {
fc := clock.NewFake()
mr := &mockRevoker{}
bkr := &badKeyRevoker{dbMap: dbMap, raClient: mr, clk: fc}
bkr := &badKeyRevoker{
dbMap: dbMap,
raClient: mr,
clk: fc,
}
err = bkr.revokeCerts([]unrevokedCertificate{
{ID: 0, Serial: "ff"},
@ -270,11 +297,20 @@ func TestCertificateAbsent(t *testing.T) {
defer test.ResetBoulderTestDatabase(t)()
fc := clock.NewFake()
bkr := &badKeyRevoker{
dbMap: dbMap,
maxRevocations: 1,
serialBatchSize: 1,
raClient: &mockRevoker{},
logger: blog.NewMock(),
clk: fc,
maxExpectedReplicationLag: time.Second * 22,
}
// populate DB with all the test data
regIDA := insertRegistration(t, dbMap, fc)
hashA := randHash(t)
insertBlockedRow(t, dbMap, fc, hashA, regIDA, false)
insertBlockedRow(t, dbMap, fcBeforeRepLag(fc, bkr), hashA, regIDA, false)
// Add an entry to keyHashToSerial but not to certificateStatus or certificate
// status, and expect an error.
@ -287,14 +323,6 @@ func TestCertificateAbsent(t *testing.T) {
)
test.AssertNotError(t, err, "failed to insert test keyHashToSerial row")
bkr := &badKeyRevoker{
dbMap: dbMap,
maxRevocations: 1,
serialBatchSize: 1,
raClient: &mockRevoker{},
logger: blog.NewMock(),
clk: fc,
}
_, err = bkr.invoke(ctx)
test.AssertError(t, err, "expected error when row in keyHashToSerial didn't have a matching cert")
}
@ -310,12 +338,13 @@ func TestInvoke(t *testing.T) {
mr := &mockRevoker{}
bkr := &badKeyRevoker{
dbMap: dbMap,
maxRevocations: 10,
serialBatchSize: 1,
raClient: mr,
logger: blog.NewMock(),
clk: fc,
dbMap: dbMap,
maxRevocations: 10,
serialBatchSize: 1,
raClient: mr,
logger: blog.NewMock(),
clk: fc,
maxExpectedReplicationLag: time.Second * 22,
}
// populate DB with all the test data
@ -324,7 +353,7 @@ func TestInvoke(t *testing.T) {
regIDC := insertRegistration(t, dbMap, fc)
regIDD := insertRegistration(t, dbMap, fc)
hashA := randHash(t)
insertBlockedRow(t, dbMap, fc, hashA, regIDC, false)
insertBlockedRow(t, dbMap, fcBeforeRepLag(fc, bkr), hashA, regIDC, false)
insertGoodCert(t, dbMap, fc, hashA, "ff", regIDA)
insertGoodCert(t, dbMap, fc, hashA, "ee", regIDB)
insertGoodCert(t, dbMap, fc, hashA, "dd", regIDC)
@ -345,7 +374,7 @@ func TestInvoke(t *testing.T) {
// add a row with no associated valid certificates
hashB := randHash(t)
insertBlockedRow(t, dbMap, fc, hashB, regIDC, false)
insertBlockedRow(t, dbMap, fcBeforeRepLag(fc, bkr), hashB, regIDC, false)
insertCert(t, dbMap, fc, hashB, "bb", regIDA, Expired, Revoked)
noWork, err = bkr.invoke(ctx)
@ -376,11 +405,12 @@ func TestInvokeRevokerHasNoExtantCerts(t *testing.T) {
mr := &mockRevoker{}
bkr := &badKeyRevoker{dbMap: dbMap,
maxRevocations: 10,
serialBatchSize: 1,
raClient: mr,
logger: blog.NewMock(),
clk: fc,
maxRevocations: 10,
serialBatchSize: 1,
raClient: mr,
logger: blog.NewMock(),
clk: fc,
maxExpectedReplicationLag: time.Second * 22,
}
// populate DB with all the test data
@ -390,7 +420,7 @@ func TestInvokeRevokerHasNoExtantCerts(t *testing.T) {
hashA := randHash(t)
insertBlockedRow(t, dbMap, fc, hashA, regIDA, false)
insertBlockedRow(t, dbMap, fcBeforeRepLag(fc, bkr), hashA, regIDA, false)
insertGoodCert(t, dbMap, fc, hashA, "ee", regIDB)
insertGoodCert(t, dbMap, fc, hashA, "dd", regIDB)

View File

@ -172,8 +172,8 @@ func main() {
if c.CA.HostnamePolicyFile == "" {
cmd.Fail("HostnamePolicyFile was empty")
}
err = pa.LoadHostnamePolicyFile(c.CA.HostnamePolicyFile)
cmd.FailOnError(err, "Couldn't load hostname policy file")
err = pa.LoadIdentPolicyFile(c.CA.HostnamePolicyFile)
cmd.FailOnError(err, "Couldn't load identifier policy file")
// Do this before creating the issuers to ensure the log list is loaded before
// the linters are initialized.

View File

@ -175,8 +175,8 @@ func main() {
if c.RA.HostnamePolicyFile == "" {
cmd.Fail("HostnamePolicyFile must be provided.")
}
err = pa.LoadHostnamePolicyFile(c.RA.HostnamePolicyFile)
cmd.FailOnError(err, "Couldn't load hostname policy file")
err = pa.LoadIdentPolicyFile(c.RA.HostnamePolicyFile)
cmd.FailOnError(err, "Couldn't load identifier policy file")
tlsConfig, err := c.RA.TLS.Load(scope)
cmd.FailOnError(err, "TLS config")

View File

@ -1,21 +1,20 @@
# `ceremony`
```
```sh
ceremony --config path/to/config.yml
```
`ceremony` is a tool designed for Certificate Authority specific key and certificate ceremonies. The main design principle is that unlike most ceremony tooling there is a single user input, a configuration file, which is required to complete a root, intermediate, or key ceremony. The goal is to make ceremonies as simple as possible and allow for simple verification of a single file, instead of verification of a large number of independent commands.
`ceremony` has these modes:
* `root` - generates a signing key on HSM and creates a self-signed root certificate that uses the generated key, outputting a PEM public key, and a PEM certificate. After generating such a root for public trust purposes, it should be submitted to [as many root programs as is possible/practical](https://github.com/daknob/root-programs).
* `intermediate` - creates a intermediate certificate and signs it using a signing key already on a HSM, outputting a PEM certificate
* `cross-csr` - creates a CSR for signing by a third party, outputting a PEM CSR.
* `cross-certificate` - issues a certificate for one root, signed by another root. This is distinct from an intermediate because there is no path length constraint and there are no EKUs.
* `ocsp-signer` - creates a delegated OCSP signing certificate and signs it using a signing key already on a HSM, outputting a PEM certificate
* `crl-signer` - creates a delegated CRL signing certificate and signs it using a signing key already on a HSM, outputting a PEM certificate
* `key` - generates a signing key on HSM, outputting a PEM public key
* `ocsp-response` - creates a OCSP response for the provided certificate and signs it using a signing key already on a HSM, outputting a base64 encoded response
* `crl` - creates a CRL with the IDP extension and `onlyContainsCACerts = true` from the provided profile and signs it using a signing key already on a HSM, outputting a PEM CRL
- `root`: generates a signing key on HSM and creates a self-signed root certificate that uses the generated key, outputting a PEM public key, and a PEM certificate. After generating such a root for public trust purposes, it should be submitted to [as many root programs as is possible/practical](https://github.com/daknob/root-programs).
- `intermediate`: creates a intermediate certificate and signs it using a signing key already on a HSM, outputting a PEM certificate
- `cross-csr`: creates a CSR for signing by a third party, outputting a PEM CSR.
- `cross-certificate`: issues a certificate for one root, signed by another root. This is distinct from an intermediate because there is no path length constraint and there are no EKUs.
- `key`: generates a signing key on HSM, outputting a PEM public key
- `ocsp-response`: creates a OCSP response for the provided certificate and signs it using a signing key already on a HSM, outputting a base64 encoded response
- `crl`: creates a CRL with the IDP extension and `onlyContainsCACerts = true` from the provided profile and signs it using a signing key already on a HSM, outputting a PEM CRL
These modes are set in the `ceremony-type` field of the configuration file.
@ -29,23 +28,29 @@ This tool always generates key pairs such that the public and private key are bo
- `ceremony-type`: string describing the ceremony type, `root`.
- `pkcs11`: object containing PKCS#11 related fields.
| Field | Description |
| --- | --- |
| `module` | Path to the PKCS#11 module to use to communicate with a HSM. |
| `pin` | Specifies the login PIN, should only be provided if the HSM device requires one to interact with the slot. |
| `store-key-in-slot` | Specifies which HSM object slot the generated signing key should be stored in. |
| `store-key-with-label` | Specifies the HSM object label for the generated signing key. Both public and private key objects are stored with this label. |
- `key`: object containing key generation related fields.
| Field | Description |
| --- | --- |
| `type` | Specifies the type of key to be generated, either `rsa` or `ecdsa`. If `rsa` the generated key will have an exponent of 65537 and a modulus length specified by `rsa-mod-length`. If `ecdsa` the curve is specified by `ecdsa-curve`. |
| `ecdsa-curve` | Specifies the ECDSA curve to use when generating key, either `P-224`, `P-256`, `P-384`, or `P-521`. |
| `rsa-mod-length` | Specifies the length of the RSA modulus, either `2048` or `4096`.
| `rsa-mod-length` | Specifies the length of the RSA modulus, either `2048` or `4096`. |
- `outputs`: object containing paths to write outputs.
| Field | Description |
| --- | --- |
| `public-key-path` | Path to store generated PEM public key. |
| `certificate-path` | Path to store signed PEM certificate. |
- `certificate-profile`: object containing profile for certificate to generate. Fields are documented [below](#certificate-profile-format).
Example:
@ -76,25 +81,31 @@ certificate-profile:
This config generates a ECDSA P-384 key in the HSM with the object label `root signing key` and uses this key to sign a self-signed certificate. The public key for the key generated is written to `/home/user/root-signing-pub.pem` and the certificate is written to `/home/user/root-cert.pem`.
### Intermediate or Cross-Certificate ceremony
### Intermediate ceremony
- `ceremony-type`: string describing the ceremony type, `intermediate` or `cross-certificate`.
- `ceremony-type`: string describing the ceremony type, `intermediate`.
- `pkcs11`: object containing PKCS#11 related fields.
| Field | Description |
| --- | --- |
| `module` | Path to the PKCS#11 module to use to communicate with a HSM. |
| `pin` | Specifies the login PIN, should only be provided if the HSM device requires one to interact with the slot. |
| `signing-key-slot` | Specifies which HSM object slot the signing key is in. |
| `signing-key-label` | Specifies the HSM object label for the signing keypair's public key. |
- `inputs`: object containing paths for inputs
| Field | Description |
| --- | --- |
| `public-key-path` | Path to PEM subject public key for certificate. |
| `issuer-certificate-path` | Path to PEM issuer certificate. |
| `public-key-path` | Path to PEM subject public key for certificate. |
- `outputs`: object containing paths to write outputs.
| Field | Description |
| --- | --- |
| `certificate-path` | Path to store signed PEM certificate. |
- `certificate-profile`: object containing profile for certificate to generate. Fields are documented [below](#certificate-profile-format).
Example:
@ -106,8 +117,8 @@ pkcs11:
signing-key-slot: 0
signing-key-label: root signing key
inputs:
public-key-path: /home/user/intermediate-signing-pub.pem
issuer-certificate-path: /home/user/root-cert.pem
public-key-path: /home/user/intermediate-signing-pub.pem
outputs:
certificate-path: /home/user/intermediate-cert.pem
certificate-profile:
@ -131,26 +142,95 @@ certificate-profile:
This config generates an intermediate certificate signed by a key in the HSM, identified by the object label `root signing key` and the object ID `ffff`. The subject key used is taken from `/home/user/intermediate-signing-pub.pem` and the issuer is `/home/user/root-cert.pem`, the resulting certificate is written to `/home/user/intermediate-cert.pem`.
Note: Intermediate certificates always include the extended key usages id-kp-serverAuth as required by 7.1.2.2.g of the CABF Baseline Requirements. Since we also include id-kp-clientAuth in end-entity certificates in boulder we also include it in intermediates, if this changes we may remove this inclusion.
Note: Intermediate certificates always include the extended key usages id-kp-serverAuth as required by 7.1.2.2.g of the CABF Baseline Requirements.
### Cross-CSR ceremony
### Cross-Certificate ceremony
- `ceremony-type`: string describing the ceremony type, `cross-csr`.
- `ceremony-type`: string describing the ceremony type, `cross-certificate`.
- `pkcs11`: object containing PKCS#11 related fields.
| Field | Description |
| --- | --- |
| `module` | Path to the PKCS#11 module to use to communicate with a HSM. |
| `pin` | Specifies the login PIN, should only be provided if the HSM device requires one to interact with the slot. |
| `signing-key-slot` | Specifies which HSM object slot the signing key is in. |
| `signing-key-label` | Specifies the HSM object label for the signing keypair's public key. |
- `inputs`: object containing paths for inputs
| Field | Description |
| --- | --- |
| `issuer-certificate-path` | Path to PEM issuer certificate. |
| `public-key-path` | Path to PEM subject public key for certificate. |
| `certificate-to-cross-sign-path` | Path to PEM self-signed certificate that this ceremony is a cross-sign of. |
- `outputs`: object containing paths to write outputs.
| Field | Description |
| --- | --- |
| `certificate-path` | Path to store signed PEM certificate. |
- `certificate-profile`: object containing profile for certificate to generate. Fields are documented [below](#certificate-profile-format).
Example:
```yaml
ceremony-type: cross-certificate
pkcs11:
module: /usr/lib/opensc-pkcs11.so
signing-key-slot: 0
signing-key-label: root signing key
inputs:
issuer-certificate-path: /home/user/root-cert.pem
public-key-path: /home/user/root-signing-pub-2.pem
certificate-to-cross-sign-path: /home/user/root-cert-2.pem
outputs:
certificate-path: /home/user/root-cert-2-cross.pem
certificate-profile:
signature-algorithm: ECDSAWithSHA384
common-name: CA root 2
organization: good guys
country: US
not-before: 2020-01-01 12:00:00
not-after: 2040-01-01 12:00:00
ocsp-url: http://good-guys.com/ocsp
crl-url: http://good-guys.com/crl
issuer-url: http://good-guys.com/root
policies:
- oid: 1.2.3
- oid: 4.5.6
key-usages:
- Digital Signature
- Cert Sign
- CRL Sign
```
This config generates a cross-sign of the already-created "CA root 2", issued from the similarly-already-created "CA root". The subject key used is taken from `/home/user/root-signing-pub-2.pem`. The EKUs and Subject Key Identifier are taken from `/home/user/root-cert-2-cross.pem`. The issuer is `/home/user/root-cert.pem`, and the Issuer and Authority Key Identifier fields are taken from that cert. The resulting certificate is written to `/home/user/root-cert-2-cross.pem`.
### Cross-CSR ceremony
- `ceremony-type`: string describing the ceremony type, `cross-csr`.
- `pkcs11`: object containing PKCS#11 related fields.
| Field | Description |
| --- | --- |
| `module` | Path to the PKCS#11 module to use to communicate with a HSM. |
| `pin` | Specifies the login PIN, should only be provided if the HSM device requires one to interact with the slot. |
| `signing-key-slot` | Specifies which HSM object slot the signing key is in. |
| `signing-key-label` | Specifies the HSM object label for the signing keypair's public key. |
- `inputs`: object containing paths for inputs
| Field | Description |
| --- | --- |
| `public-key-path` | Path to PEM subject public key for certificate. |
- `outputs`: object containing paths to write outputs.
| Field | Description |
| --- | --- |
| `csr-path` | Path to store PEM CSR for cross-signing, optional. |
- `certificate-profile`: object containing profile for certificate to generate. Fields are documented [below](#certificate-profile-format). Should only include Subject related fields `common-name`, `organization`, `country`.
Example:
@ -173,119 +253,28 @@ certificate-profile:
This config generates a CSR signed by a key in the HSM, identified by the object label `intermediate signing key`, and writes it to `/home/user/csr.pem`.
### OCSP Signing Certificate ceremony
- `ceremony-type`: string describing the ceremony type, `ocsp-signer`.
- `pkcs11`: object containing PKCS#11 related fields.
| Field | Description |
| --- | --- |
| `module` | Path to the PKCS#11 module to use to communicate with a HSM. |
| `pin` | Specifies the login PIN, should only be provided if the HSM device requires one to interact with the slot. |
| `signing-key-slot` | Specifies which HSM object slot the signing key is in. |
| `signing-key-label` | Specifies the HSM object label for the signing keypair's public key. |
- `inputs`: object containing paths for inputs
| Field | Description |
| --- | --- |
| `public-key-path` | Path to PEM subject public key for certificate. |
| `issuer-certificate-path` | Path to PEM issuer certificate. |
- `outputs`: object containing paths to write outputs.
| Field | Description |
| --- | --- |
| `certificate-path` | Path to store signed PEM certificate. |
- `certificate-profile`: object containing profile for certificate to generate. Fields are documented [below](#certificate-profile-format). The key-usages, ocsp-url, and crl-url fields must not be set.
When generating an OCSP signing certificate the key usages field will be set to just Digital Signature and an EKU extension will be included with the id-kp-OCSPSigning usage. Additionally an id-pkix-ocsp-nocheck extension will be included in the certificate.
Example:
```yaml
ceremony-type: ocsp-signer
pkcs11:
module: /usr/lib/opensc-pkcs11.so
signing-key-slot: 0
signing-key-label: intermediate signing key
inputs:
public-key-path: /home/user/ocsp-signer-signing-pub.pem
issuer-certificate-path: /home/user/intermediate-cert.pem
outputs:
certificate-path: /home/user/ocsp-signer-cert.pem
certificate-profile:
signature-algorithm: ECDSAWithSHA384
common-name: CA OCSP signer
organization: good guys
country: US
not-before: 2020-01-01 12:00:00
not-after: 2040-01-01 12:00:00
issuer-url: http://good-guys.com/root
```
This config generates a delegated OCSP signing certificate signed by a key in the HSM, identified by the object label `intermediate signing key` and the object ID `ffff`. The subject key used is taken from `/home/user/ocsp-signer-signing-pub.pem` and the issuer is `/home/user/intermediate-cert.pem`, the resulting certificate is written to `/home/user/ocsp-signer-cert.pem`.
### CRL Signing Certificate ceremony
- `ceremony-type`: string describing the ceremony type, `crl-signer`.
- `pkcs11`: object containing PKCS#11 related fields.
| Field | Description |
| --- | --- |
| `module` | Path to the PKCS#11 module to use to communicate with a HSM. |
| `pin` | Specifies the login PIN, should only be provided if the HSM device requires one to interact with the slot. |
| `signing-key-slot` | Specifies which HSM object slot the signing key is in. |
| `signing-key-label` | Specifies the HSM object label for the signing keypair's public key. |
- `inputs`: object containing paths for inputs
| Field | Description |
| --- | --- |
| `public-key-path` | Path to PEM subject public key for certificate. |
| `issuer-certificate-path` | Path to PEM issuer certificate. |
- `outputs`: object containing paths to write outputs.
| Field | Description |
| --- | --- |
| `certificate-path` | Path to store signed PEM certificate. |
- `certificate-profile`: object containing profile for certificate to generate. Fields are documented [below](#certificate-profile-format). The key-usages, ocsp-url, and crl-url fields must not be set.
When generating a CRL signing certificate the key usages field will be set to just CRL Sign.
Example:
```yaml
ceremony-type: crl-signer
pkcs11:
module: /usr/lib/opensc-pkcs11.so
signing-key-slot: 0
signing-key-label: intermediate signing key
inputs:
public-key-path: /home/user/crl-signer-signing-pub.pem
issuer-certificate-path: /home/user/intermediate-cert.pem
outputs:
certificate-path: /home/user/crl-signer-cert.pem
certificate-profile:
signature-algorithm: ECDSAWithSHA384
common-name: CA CRL signer
organization: good guys
country: US
not-before: 2020-01-01 12:00:00
not-after: 2040-01-01 12:00:00
issuer-url: http://good-guys.com/root
```
This config generates a delegated CRL signing certificate signed by a key in the HSM, identified by the object label `intermediate signing key` and the object ID `ffff`. The subject key used is taken from `/home/user/crl-signer-signing-pub.pem` and the issuer is `/home/user/intermediate-cert.pem`, the resulting certificate is written to `/home/user/crl-signer-cert.pem`.
### Key ceremony
- `ceremony-type`: string describing the ceremony type, `key`.
- `pkcs11`: object containing PKCS#11 related fields.
| Field | Description |
| --- | --- |
| `module` | Path to the PKCS#11 module to use to communicate with a HSM. |
| `pin` | Specifies the login PIN, should only be provided if the HSM device requires one to interact with the slot. |
| `store-key-in-slot` | Specifies which HSM object slot the generated signing key should be stored in. |
| `store-key-with-label` | Specifies the HSM object label for the generated signing key. Both public and private key objects are stored with this label. |
- `key`: object containing key generation related fields.
| Field | Description |
| --- | --- |
| `type` | Specifies the type of key to be generated, either `rsa` or `ecdsa`. If `rsa` the generated key will have an exponent of 65537 and a modulus length specified by `rsa-mod-length`. If `ecdsa` the curve is specified by `ecdsa-curve`. |
| `ecdsa-curve` | Specifies the ECDSA curve to use when generating key, either `P-224`, `P-256`, `P-384`, or `P-521`. |
| `rsa-mod-length` | Specifies the length of the RSA modulus, either `2048` or `4096`.
| `rsa-mod-length` | Specifies the length of the RSA modulus, either `2048` or `4096`. |
- `outputs`: object containing paths to write outputs.
| Field | Description |
| --- | --- |
| `public-key-path` | Path to store generated PEM public key. |
@ -311,23 +300,30 @@ This config generates an ECDSA P-384 key in the HSM with the object label `inter
- `ceremony-type`: string describing the ceremony type, `ocsp-response`.
- `pkcs11`: object containing PKCS#11 related fields.
| Field | Description |
| --- | --- |
| `module` | Path to the PKCS#11 module to use to communicate with a HSM. |
| `pin` | Specifies the login PIN, should only be provided if the HSM device requires one to interact with the slot. |
| `signing-key-slot` | Specifies which HSM object slot the signing key is in. |
| `signing-key-label` | Specifies the HSM object label for the signing keypair's public key. |
- `inputs`: object containing paths for inputs
| Field | Description |
| --- | --- |
| `certificate-path` | Path to PEM certificate to create a response for. |
| `issuer-certificate-path` | Path to PEM issuer certificate. |
| `delegated-issuer-certificate-path` | Path to PEM delegated issuer certificate, if one is being used. |
- `outputs`: object containing paths to write outputs.
| Field | Description |
| --- | --- |
| `response-path` | Path to store signed base64 encoded response. |
- `ocsp-profile`: object containing profile for the OCSP response.
| Field | Description |
| --- | --- |
| `this-update` | Specifies the OCSP response thisUpdate date, in the format `2006-01-02 15:04:05`. The time will be interpreted as UTC. |
@ -359,21 +355,28 @@ This config generates a OCSP response signed by a key in the HSM, identified by
- `ceremony-type`: string describing the ceremony type, `crl`.
- `pkcs11`: object containing PKCS#11 related fields.
| Field | Description |
| --- | --- |
| `module` | Path to the PKCS#11 module to use to communicate with a HSM. |
| `pin` | Specifies the login PIN, should only be provided if the HSM device requires one to interact with the slot. |
| `signing-key-slot` | Specifies which HSM object slot the signing key is in. |
| `signing-key-label` | Specifies the HSM object label for the signing keypair's public key. |
- `inputs`: object containing paths for inputs
| Field | Description |
| --- | --- |
| `issuer-certificate-path` | Path to PEM issuer certificate. |
- `outputs`: object containing paths to write outputs.
| Field | Description |
| --- | --- |
| `crl-path` | Path to store signed PEM CRL. |
- `crl-profile`: object containing profile for the CRL.
| Field | Description |
| --- | --- |
| `this-update` | Specifies the CRL thisUpdate date, in the format `2006-01-02 15:04:05`. The time will be interpreted as UTC. |

View File

@ -76,8 +76,6 @@ type certType int
const (
rootCert certType = iota
intermediateCert
ocspCert
crlCert
crossCert
requestCert
)
@ -153,23 +151,12 @@ func (profile *certProfile) verifyProfile(ct certType) error {
}
// BR 7.1.2.10.5 CA Certificate Certificate Policies
// OID 2.23.140.1.2.1 is an anyPolicy
// OID 2.23.140.1.2.1 is CABF BRs Domain Validated
if len(profile.Policies) != 1 || profile.Policies[0].OID != "2.23.140.1.2.1" {
return errors.New("policy should be exactly BRs domain-validated for subordinate CAs")
}
}
if ct == ocspCert || ct == crlCert {
if len(profile.KeyUsages) != 0 {
return errors.New("key-usages cannot be set for a delegated signer")
}
if profile.CRLURL != "" {
return errors.New("crl-url cannot be set for a delegated signer")
}
if profile.OCSPURL != "" {
return errors.New("ocsp-url cannot be set for a delegated signer")
}
}
return nil
}
@ -194,8 +181,6 @@ var stringToKeyUsage = map[string]x509.KeyUsage{
"Cert Sign": x509.KeyUsageCertSign,
}
var oidOCSPNoCheck = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 48, 1, 5}
func generateSKID(pk []byte) ([]byte, error) {
var pkixPublicKey struct {
Algo pkix.AlgorithmIdentifier
@ -252,11 +237,6 @@ func makeTemplate(randReader io.Reader, profile *certProfile, pubKey []byte, tbc
}
ku |= kuBit
}
if ct == ocspCert {
ku = x509.KeyUsageDigitalSignature
} else if ct == crlCert {
ku = x509.KeyUsageCRLSign
}
if ku == 0 {
return nil, errors.New("at least one key usage must be set")
}
@ -296,14 +276,6 @@ func makeTemplate(randReader io.Reader, profile *certProfile, pubKey []byte, tbc
// BR 7.1.2.1.2 Root CA Extensions
// Extension Presence Critical Description
// extKeyUsage MUST NOT N -
case ocspCert:
cert.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageOCSPSigning}
// ASN.1 NULL is 0x05, 0x00
ocspNoCheckExt := pkix.Extension{Id: oidOCSPNoCheck, Value: []byte{5, 0}}
cert.ExtraExtensions = append(cert.ExtraExtensions, ocspNoCheckExt)
cert.IsCA = false
case crlCert:
cert.IsCA = false
case requestCert, intermediateCert:
// id-kp-serverAuth is included in intermediate certificates, as required by
// Section 7.1.2.10.6 of the CA/BF Baseline Requirements.
@ -314,6 +286,8 @@ func makeTemplate(randReader io.Reader, profile *certProfile, pubKey []byte, tbc
case crossCert:
cert.ExtKeyUsage = tbcs.ExtKeyUsage
cert.MaxPathLenZero = tbcs.MaxPathLenZero
// The SKID needs to match the previous SKID, no matter how it was computed.
cert.SubjectKeyId = tbcs.SubjectKeyId
}
for _, policyConfig := range profile.Policies {

View File

@ -1,7 +1,6 @@
package main
import (
"bytes"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
@ -174,73 +173,6 @@ func TestMakeTemplateRestrictedCrossCertificate(t *testing.T) {
test.AssertEquals(t, cert.ExtKeyUsage[0], x509.ExtKeyUsageServerAuth)
}
func TestMakeTemplateOCSP(t *testing.T) {
s, ctx := pkcs11helpers.NewSessionWithMock()
ctx.GenerateRandomFunc = realRand
randReader := newRandReader(s)
profile := &certProfile{
SignatureAlgorithm: "SHA256WithRSA",
CommonName: "common name",
Organization: "organization",
Country: "country",
OCSPURL: "ocsp",
CRLURL: "crl",
IssuerURL: "issuer",
NotAfter: "2018-05-18 11:31:00",
NotBefore: "2018-05-18 11:31:00",
}
pubKey := samplePubkey()
cert, err := makeTemplate(randReader, profile, pubKey, nil, ocspCert)
test.AssertNotError(t, err, "makeTemplate failed")
test.Assert(t, !cert.IsCA, "IsCA is set")
// Check KU is only KeyUsageDigitalSignature
test.AssertEquals(t, cert.KeyUsage, x509.KeyUsageDigitalSignature)
// Check there is a single EKU with id-kp-OCSPSigning
test.AssertEquals(t, len(cert.ExtKeyUsage), 1)
test.AssertEquals(t, cert.ExtKeyUsage[0], x509.ExtKeyUsageOCSPSigning)
// Check ExtraExtensions contains a single id-pkix-ocsp-nocheck
hasExt := false
asnNULL := []byte{5, 0}
for _, ext := range cert.ExtraExtensions {
if ext.Id.Equal(oidOCSPNoCheck) {
if hasExt {
t.Error("template contains multiple id-pkix-ocsp-nocheck extensions")
}
hasExt = true
if !bytes.Equal(ext.Value, asnNULL) {
t.Errorf("id-pkix-ocsp-nocheck has unexpected content: want %x, got %x", asnNULL, ext.Value)
}
}
}
test.Assert(t, hasExt, "template doesn't contain id-pkix-ocsp-nocheck extensions")
}
func TestMakeTemplateCRL(t *testing.T) {
s, ctx := pkcs11helpers.NewSessionWithMock()
ctx.GenerateRandomFunc = realRand
randReader := newRandReader(s)
profile := &certProfile{
SignatureAlgorithm: "SHA256WithRSA",
CommonName: "common name",
Organization: "organization",
Country: "country",
OCSPURL: "ocsp",
CRLURL: "crl",
IssuerURL: "issuer",
NotAfter: "2018-05-18 11:31:00",
NotBefore: "2018-05-18 11:31:00",
}
pubKey := samplePubkey()
cert, err := makeTemplate(randReader, profile, pubKey, nil, crlCert)
test.AssertNotError(t, err, "makeTemplate failed")
test.Assert(t, !cert.IsCA, "IsCA is set")
test.AssertEquals(t, cert.KeyUsage, x509.KeyUsageCRLSign)
}
func TestVerifyProfile(t *testing.T) {
for _, tc := range []struct {
profile certProfile
@ -366,114 +298,6 @@ func TestVerifyProfile(t *testing.T) {
},
certType: []certType{rootCert},
},
{
profile: certProfile{
NotBefore: "a",
NotAfter: "b",
SignatureAlgorithm: "c",
CommonName: "d",
Organization: "e",
Country: "f",
IssuerURL: "g",
KeyUsages: []string{"j"},
},
certType: []certType{ocspCert},
expectedErr: "key-usages cannot be set for a delegated signer",
},
{
profile: certProfile{
NotBefore: "a",
NotAfter: "b",
SignatureAlgorithm: "c",
CommonName: "d",
Organization: "e",
Country: "f",
IssuerURL: "g",
CRLURL: "i",
},
certType: []certType{ocspCert},
expectedErr: "crl-url cannot be set for a delegated signer",
},
{
profile: certProfile{
NotBefore: "a",
NotAfter: "b",
SignatureAlgorithm: "c",
CommonName: "d",
Organization: "e",
Country: "f",
IssuerURL: "g",
OCSPURL: "h",
},
certType: []certType{ocspCert},
expectedErr: "ocsp-url cannot be set for a delegated signer",
},
{
profile: certProfile{
NotBefore: "a",
NotAfter: "b",
SignatureAlgorithm: "c",
CommonName: "d",
Organization: "e",
Country: "f",
IssuerURL: "g",
},
certType: []certType{ocspCert},
},
{
profile: certProfile{
NotBefore: "a",
NotAfter: "b",
SignatureAlgorithm: "c",
CommonName: "d",
Organization: "e",
Country: "f",
IssuerURL: "g",
KeyUsages: []string{"j"},
},
certType: []certType{crlCert},
expectedErr: "key-usages cannot be set for a delegated signer",
},
{
profile: certProfile{
NotBefore: "a",
NotAfter: "b",
SignatureAlgorithm: "c",
CommonName: "d",
Organization: "e",
Country: "f",
IssuerURL: "g",
CRLURL: "i",
},
certType: []certType{crlCert},
expectedErr: "crl-url cannot be set for a delegated signer",
},
{
profile: certProfile{
NotBefore: "a",
NotAfter: "b",
SignatureAlgorithm: "c",
CommonName: "d",
Organization: "e",
Country: "f",
IssuerURL: "g",
OCSPURL: "h",
},
certType: []certType{crlCert},
expectedErr: "ocsp-url cannot be set for a delegated signer",
},
{
profile: certProfile{
NotBefore: "a",
NotAfter: "b",
SignatureAlgorithm: "c",
CommonName: "d",
Organization: "e",
Country: "f",
IssuerURL: "g",
},
certType: []certType{crlCert},
},
{
profile: certProfile{
NotBefore: "a",

View File

@ -7,8 +7,9 @@ import (
"fmt"
"log"
"github.com/letsencrypt/boulder/pkcs11helpers"
"github.com/miekg/pkcs11"
"github.com/letsencrypt/boulder/pkcs11helpers"
)
var stringToCurve = map[string]elliptic.Curve{
@ -70,7 +71,7 @@ func ecPub(
return nil, err
}
if pubKey.Curve != expectedCurve {
return nil, errors.New("Returned EC parameters doesn't match expected curve")
return nil, errors.New("returned EC parameters doesn't match expected curve")
}
log.Printf("\tX: %X\n", pubKey.X.Bytes())
log.Printf("\tY: %X\n", pubKey.Y.Bytes())

View File

@ -7,8 +7,9 @@ import (
"fmt"
"log"
"github.com/letsencrypt/boulder/pkcs11helpers"
"github.com/miekg/pkcs11"
"github.com/letsencrypt/boulder/pkcs11helpers"
)
type hsmRandReader struct {
@ -49,7 +50,7 @@ func generateKey(session *pkcs11helpers.Session, label string, outputPath string
{Type: pkcs11.CKA_LABEL, Value: []byte(label)},
})
if err != pkcs11helpers.ErrNoObject {
return nil, fmt.Errorf("expected no preexisting objects with label %q in slot for key storage. got error: %s", label, err)
return nil, fmt.Errorf("expected no preexisting objects with label %q in slot for key storage. got error: %w", label, err)
}
var pubKey crypto.PublicKey
@ -58,25 +59,25 @@ func generateKey(session *pkcs11helpers.Session, label string, outputPath string
case "rsa":
pubKey, keyID, err = rsaGenerate(session, label, config.RSAModLength)
if err != nil {
return nil, fmt.Errorf("failed to generate RSA key pair: %s", err)
return nil, fmt.Errorf("failed to generate RSA key pair: %w", err)
}
case "ecdsa":
pubKey, keyID, err = ecGenerate(session, label, config.ECDSACurve)
if err != nil {
return nil, fmt.Errorf("failed to generate ECDSA key pair: %s", err)
return nil, fmt.Errorf("failed to generate ECDSA key pair: %w", err)
}
}
der, err := x509.MarshalPKIXPublicKey(pubKey)
if err != nil {
return nil, fmt.Errorf("Failed to marshal public key: %s", err)
return nil, fmt.Errorf("failed to marshal public key: %w", err)
}
pemBytes := pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: der})
log.Printf("Public key PEM:\n%s\n", pemBytes)
err = writeFile(outputPath, pemBytes)
if err != nil {
return nil, fmt.Errorf("Failed to write public key to %q: %s", outputPath, err)
return nil, fmt.Errorf("failed to write public key to %q: %w", outputPath, err)
}
log.Printf("Public key written to %q\n", outputPath)

View File

@ -239,7 +239,7 @@ type intermediateConfig struct {
SkipLints []string `yaml:"skip-lints"`
}
func (ic intermediateConfig) validate(ct certType) error {
func (ic intermediateConfig) validate() error {
err := ic.PKCS11.validate()
if err != nil {
return err
@ -260,7 +260,7 @@ func (ic intermediateConfig) validate(ct certType) error {
}
// Certificate profile
err = ic.CertProfile.verifyProfile(ct)
err = ic.CertProfile.verifyProfile(intermediateCert)
if err != nil {
return err
}
@ -504,7 +504,7 @@ func loadCert(filename string) (*x509.Certificate, error) {
log.Printf("Loaded certificate from %s\n", filename)
block, _ := pem.Decode(certPEM)
if block == nil {
return nil, fmt.Errorf("No data in cert PEM file %s", filename)
return nil, fmt.Errorf("no data in cert PEM file %q", filename)
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
@ -599,7 +599,7 @@ func loadPubKey(filename string) (crypto.PublicKey, []byte, error) {
log.Printf("Loaded public key from %s\n", filename)
block, _ := pem.Decode(keyPEM)
if block == nil {
return nil, nil, fmt.Errorf("No data in cert PEM file %s", filename)
return nil, nil, fmt.Errorf("no data in cert PEM file %q", filename)
}
key, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
@ -658,17 +658,14 @@ func rootCeremony(configBytes []byte) error {
return nil
}
func intermediateCeremony(configBytes []byte, ct certType) error {
if ct != intermediateCert && ct != ocspCert && ct != crlCert {
return fmt.Errorf("wrong certificate type provided")
}
func intermediateCeremony(configBytes []byte) error {
var config intermediateConfig
err := strictyaml.Unmarshal(configBytes, &config)
if err != nil {
return fmt.Errorf("failed to parse config: %s", err)
}
log.Printf("Preparing intermediate ceremony for %s\n", config.Outputs.CertificatePath)
err = config.validate(ct)
err = config.validate()
if err != nil {
return fmt.Errorf("failed to validate config: %s", err)
}
@ -684,7 +681,7 @@ func intermediateCeremony(configBytes []byte, ct certType) error {
if err != nil {
return err
}
template, err := makeTemplate(randReader, &config.CertProfile, pubBytes, nil, ct)
template, err := makeTemplate(randReader, &config.CertProfile, pubBytes, nil, intermediateCert)
if err != nil {
return fmt.Errorf("failed to create certificate profile: %s", err)
}
@ -713,10 +710,7 @@ func intermediateCeremony(configBytes []byte, ct certType) error {
return nil
}
func crossCertCeremony(configBytes []byte, ct certType) error {
if ct != crossCert {
return fmt.Errorf("wrong certificate type provided")
}
func crossCertCeremony(configBytes []byte) error {
var config crossCertConfig
err := strictyaml.Unmarshal(configBytes, &config)
if err != nil {
@ -743,7 +737,7 @@ func crossCertCeremony(configBytes []byte, ct certType) error {
if err != nil {
return err
}
template, err := makeTemplate(randReader, &config.CertProfile, pubBytes, toBeCrossSigned, ct)
template, err := makeTemplate(randReader, &config.CertProfile, pubBytes, toBeCrossSigned, crossCert)
if err != nil {
return fmt.Errorf("failed to create certificate profile: %s", err)
}
@ -773,12 +767,24 @@ func crossCertCeremony(configBytes []byte, ct certType) error {
return fmt.Errorf("cross-signed subordinate CA's NotBefore predates the existing CA's NotBefore")
}
// BR 7.1.2.2.3 Cross-Certified Subordinate CA Extensions
// We want the Extended Key Usages of our cross-signs to be identical to those
// in the cert being cross-signed, for the sake of consistency. However, our
// Root CA Certificates do not contain any EKUs, as required by BR 7.1.2.1.2.
// Therefore, cross-signs of our roots count as "unrestricted" cross-signs per
// the definition in BR 7.1.2.2.3, and are subject to the requirement that
// the cross-sign's Issuer and Subject fields must either:
// - have identical organizationNames; or
// - have orgnaizationNames which are affiliates of each other.
// Therefore, we enforce that cross-signs with empty EKUs have identical
// Subject Organization Name fields... or allow one special case where the
// issuer is "Internet Security Research Group" and the subject is "ISRG" to
// allow us to migrate from the longer string to the shorter one.
if !slices.Equal(lintCert.ExtKeyUsage, toBeCrossSigned.ExtKeyUsage) {
return fmt.Errorf("lint cert and toBeCrossSigned cert EKUs differ")
}
if len(lintCert.ExtKeyUsage) == 0 {
// "Unrestricted" case, the issuer and subject need to be the same or at least affiliates.
if !slices.Equal(lintCert.Subject.Organization, issuer.Subject.Organization) {
if !slices.Equal(lintCert.Subject.Organization, issuer.Subject.Organization) &&
!(slices.Equal(issuer.Subject.Organization, []string{"Internet Security Research Group"}) && slices.Equal(lintCert.Subject.Organization, []string{"ISRG"})) {
return fmt.Errorf("attempted unrestricted cross-sign of certificate operated by a different organization")
}
}
@ -1044,12 +1050,12 @@ func main() {
log.Fatalf("root ceremony failed: %s", err)
}
case "cross-certificate":
err = crossCertCeremony(configBytes, crossCert)
err = crossCertCeremony(configBytes)
if err != nil {
log.Fatalf("cross-certificate ceremony failed: %s", err)
}
case "intermediate":
err = intermediateCeremony(configBytes, intermediateCert)
err = intermediateCeremony(configBytes)
if err != nil {
log.Fatalf("intermediate ceremony failed: %s", err)
}
@ -1058,11 +1064,6 @@ func main() {
if err != nil {
log.Fatalf("cross-csr ceremony failed: %s", err)
}
case "ocsp-signer":
err = intermediateCeremony(configBytes, ocspCert)
if err != nil {
log.Fatalf("ocsp signer ceremony failed: %s", err)
}
case "key":
err = keyCeremony(configBytes)
if err != nil {
@ -1078,12 +1079,7 @@ func main() {
if err != nil {
log.Fatalf("crl ceremony failed: %s", err)
}
case "crl-signer":
err = intermediateCeremony(configBytes, crlCert)
if err != nil {
log.Fatalf("crl signer ceremony failed: %s", err)
}
default:
log.Fatalf("unknown ceremony-type, must be one of: root, cross-certificate, intermediate, cross-csr, ocsp-signer, key, ocsp-response, crl, crl-signer")
log.Fatalf("unknown ceremony-type, must be one of: root, cross-certificate, intermediate, cross-csr, key, ocsp-response, crl")
}
}

View File

@ -484,7 +484,7 @@ func TestIntermediateConfigValidate(t *testing.T) {
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
err := tc.config.validate(intermediateCert)
err := tc.config.validate()
if err != nil && err.Error() != tc.expectedError {
t.Fatalf("Unexpected error, wanted: %q, got: %q", tc.expectedError, err)
} else if err == nil && tc.expectedError != "" {

View File

@ -38,8 +38,8 @@ import (
"github.com/letsencrypt/boulder/sa"
)
// For defense-in-depth in addition to using the PA & its hostnamePolicy to
// check domain names we also perform a check against the regex's from the
// For defense-in-depth in addition to using the PA & its identPolicy to check
// domain names we also perform a check against the regex's from the
// forbiddenDomains array
var forbiddenDomainPatterns = []*regexp.Regexp{
regexp.MustCompile(`^\s*$`),
@ -612,7 +612,7 @@ func main() {
pa, err := policy.New(config.PA.Identifiers, config.PA.Challenges, logger)
cmd.FailOnError(err, "Failed to create PA")
err = pa.LoadHostnamePolicyFile(config.CertChecker.HostnamePolicyFile)
err = pa.LoadIdentPolicyFile(config.CertChecker.HostnamePolicyFile)
cmd.FailOnError(err, "Failed to load HostnamePolicyFile")
if config.CertChecker.CTLogListFile != "" {

View File

@ -60,7 +60,7 @@ func init() {
if err != nil {
log.Fatal(err)
}
err = pa.LoadHostnamePolicyFile("../../test/hostname-policy.yaml")
err = pa.LoadIdentPolicyFile("../../test/ident-policy.yaml")
if err != nil {
log.Fatal(err)
}
@ -537,7 +537,7 @@ func TestIsForbiddenDomain(t *testing.T) {
// Note: These testcases are not an exhaustive representation of domains
// Boulder won't issue for, but are instead testing the defense-in-depth
// `isForbiddenDomain` function called *after* the PA has vetted the name
// against the complex hostname policy file.
// against the complex identifier policy file.
testcases := []struct {
Name string
Expected bool

View File

@ -89,15 +89,6 @@ func (d *DBConfig) URL() (string, error) {
return strings.TrimSpace(string(url)), err
}
// SMTPConfig is deprecated.
// TODO(#8199): Delete this when it is removed from bad-key-revoker's config.
type SMTPConfig struct {
PasswordConfig
Server string `validate:"required"`
Port string `validate:"required,numeric,min=1,max=65535"`
Username string `validate:"required"`
}
// PAConfig specifies how a policy authority should connect to its
// database, what policies it should enforce, and what challenges
// it should offer.

View File

@ -24,7 +24,7 @@ func init() {
func main() {
inputFilename := flag.String("input", "", "File containing a list of reversed hostnames to check, newline separated. Defaults to stdin")
policyFile := flag.String("policy", "test/hostname-policy.yaml", "File containing a hostname policy in yaml.")
policyFile := flag.String("policy", "test/ident-policy.yaml", "File containing an identifier policy in YAML.")
flag.Parse()
var input io.Reader
@ -45,7 +45,7 @@ func main() {
if err != nil {
log.Fatal(err)
}
err = pa.LoadHostnamePolicyFile(*policyFile)
err = pa.LoadIdentPolicyFile(*policyFile)
if err != nil {
log.Fatalf("reading %s: %s", *policyFile, err)
}

View File

@ -67,3 +67,8 @@ func (d *Duration) UnmarshalYAML(unmarshal func(interface{}) error) error {
d.Duration = dur
return nil
}
// MarshalYAML returns the string form of the duration, as a string.
func (d Duration) MarshalYAML() (any, error) {
return d.Duration.String(), nil
}

View File

@ -25,6 +25,11 @@ func TLSALPNChallenge01(token string) Challenge {
return newChallenge(ChallengeTypeTLSALPN01, token)
}
// DNSAccountChallenge01 constructs a dns-account-01 challenge.
func DNSAccountChallenge01(token string) Challenge {
return newChallenge(ChallengeTypeDNSAccount01, token)
}
// NewChallenge constructs a challenge of the given kind. It returns an
// error if the challenge type is unrecognized.
func NewChallenge(kind AcmeChallenge, token string) (Challenge, error) {
@ -35,6 +40,8 @@ func NewChallenge(kind AcmeChallenge, token string) (Challenge, error) {
return DNSChallenge01(token), nil
case ChallengeTypeTLSALPN01:
return TLSALPNChallenge01(token), nil
case ChallengeTypeDNSAccount01:
return DNSAccountChallenge01(token), nil
default:
return Challenge{}, fmt.Errorf("unrecognized challenge type %q", kind)
}

View File

@ -32,12 +32,16 @@ func TestChallenges(t *testing.T) {
dns01 := DNSChallenge01(token)
test.AssertNotError(t, dns01.CheckPending(), "CheckConsistencyForClientOffer returned an error")
dnsAccount01 := DNSAccountChallenge01(token)
test.AssertNotError(t, dnsAccount01.CheckPending(), "CheckConsistencyForClientOffer returned an error")
tlsalpn01 := TLSALPNChallenge01(token)
test.AssertNotError(t, tlsalpn01.CheckPending(), "CheckConsistencyForClientOffer returned an error")
test.Assert(t, ChallengeTypeHTTP01.IsValid(), "Refused valid challenge")
test.Assert(t, ChallengeTypeDNS01.IsValid(), "Refused valid challenge")
test.Assert(t, ChallengeTypeTLSALPN01.IsValid(), "Refused valid challenge")
test.Assert(t, ChallengeTypeDNSAccount01.IsValid(), "Refused valid challenge")
test.Assert(t, !AcmeChallenge("nonsense-71").IsValid(), "Accepted invalid challenge")
}

View File

@ -53,15 +53,16 @@ type AcmeChallenge string
// These types are the available challenges
const (
ChallengeTypeHTTP01 = AcmeChallenge("http-01")
ChallengeTypeDNS01 = AcmeChallenge("dns-01")
ChallengeTypeTLSALPN01 = AcmeChallenge("tls-alpn-01")
ChallengeTypeHTTP01 = AcmeChallenge("http-01")
ChallengeTypeDNS01 = AcmeChallenge("dns-01")
ChallengeTypeTLSALPN01 = AcmeChallenge("tls-alpn-01")
ChallengeTypeDNSAccount01 = AcmeChallenge("dns-account-01")
)
// IsValid tests whether the challenge is a known challenge
func (c AcmeChallenge) IsValid() bool {
switch c {
case ChallengeTypeHTTP01, ChallengeTypeDNS01, ChallengeTypeTLSALPN01:
case ChallengeTypeHTTP01, ChallengeTypeDNS01, ChallengeTypeTLSALPN01, ChallengeTypeDNSAccount01:
return true
default:
return false
@ -228,7 +229,7 @@ func (ch Challenge) RecordsSane() bool {
(ch.ValidationRecord[0].AddressUsed == netip.Addr{}) || len(ch.ValidationRecord[0].AddressesResolved) == 0 {
return false
}
case ChallengeTypeDNS01:
case ChallengeTypeDNS01, ChallengeTypeDNSAccount01:
if len(ch.ValidationRecord) > 1 {
return false
}
@ -415,9 +416,9 @@ type CertificateStatus struct {
LastExpirationNagSent time.Time `db:"lastExpirationNagSent"`
// NotAfter and IsExpired are convenience columns which allow expensive
// queries to quickly filter out certificates that we don't need to care about
// anymore. These are particularly useful for the expiration mailer and CRL
// updater. See https://github.com/letsencrypt/boulder/issues/1864.
// queries to quickly filter out certificates that we don't need to care
// about anymore. These are particularly useful for the CRL updater. See
// https://github.com/letsencrypt/boulder/issues/1864.
NotAfter time.Time `db:"notAfter"`
IsExpired bool `db:"isExpired"`
@ -429,16 +430,6 @@ type CertificateStatus struct {
IssuerNameID int64 `db:"issuerID"`
}
// FQDNSet contains the SHA256 hash of the lowercased, comma joined dNSNames
// contained in a certificate.
type FQDNSet struct {
ID int64
SetHash []byte
Serial string
Issued time.Time
Expires time.Time
}
// SCTDERs is a convenience type
type SCTDERs [][]byte

View File

@ -59,7 +59,7 @@ func TestChallengeSanityCheck(t *testing.T) {
}`), &accountKey)
test.AssertNotError(t, err, "Error unmarshaling JWK")
types := []AcmeChallenge{ChallengeTypeHTTP01, ChallengeTypeDNS01, ChallengeTypeTLSALPN01}
types := []AcmeChallenge{ChallengeTypeHTTP01, ChallengeTypeDNS01, ChallengeTypeTLSALPN01, ChallengeTypeDNSAccount01}
for _, challengeType := range types {
chall := Challenge{
Type: challengeType,
@ -152,6 +152,8 @@ func TestChallengeStringID(t *testing.T) {
test.AssertEquals(t, ch.StringID(), "iFVMwA")
ch.Type = ChallengeTypeHTTP01
test.AssertEquals(t, ch.StringID(), "0Gexug")
ch.Type = ChallengeTypeDNSAccount01
test.AssertEquals(t, ch.StringID(), "8z2wSg")
}
func TestFindChallengeByType(t *testing.T) {

View File

@ -540,7 +540,6 @@ type Registration struct {
// Next unused field number: 10
Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
Key []byte `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"`
Contact []string `protobuf:"bytes,3,rep,name=contact,proto3" json:"contact,omitempty"`
Agreement string `protobuf:"bytes,5,opt,name=agreement,proto3" json:"agreement,omitempty"`
CreatedAt *timestamppb.Timestamp `protobuf:"bytes,9,opt,name=createdAt,proto3" json:"createdAt,omitempty"`
Status string `protobuf:"bytes,8,opt,name=status,proto3" json:"status,omitempty"`
@ -592,13 +591,6 @@ func (x *Registration) GetKey() []byte {
return nil
}
func (x *Registration) GetContact() []string {
if x != nil {
return x.Contact
}
return nil
}
func (x *Registration) GetAgreement() string {
if x != nil {
return x.Agreement
@ -1006,85 +998,84 @@ var file_core_proto_rawDesc = string([]byte{
0x69, 0x73, 0x73, 0x75, 0x65, 0x72, 0x49, 0x44, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08,
0x69, 0x73, 0x73, 0x75, 0x65, 0x72, 0x49, 0x44, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x4a, 0x04,
0x08, 0x04, 0x10, 0x05, 0x4a, 0x04, 0x08, 0x05, 0x10, 0x06, 0x4a, 0x04, 0x08, 0x07, 0x10, 0x08,
0x4a, 0x04, 0x08, 0x08, 0x10, 0x09, 0x4a, 0x04, 0x08, 0x09, 0x10, 0x0a, 0x22, 0xcc, 0x01, 0x0a,
0x4a, 0x04, 0x08, 0x08, 0x10, 0x09, 0x4a, 0x04, 0x08, 0x09, 0x10, 0x0a, 0x22, 0xb8, 0x01, 0x0a,
0x0c, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a,
0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x10, 0x0a,
0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12,
0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x63, 0x74, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09,
0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x63, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x67, 0x72,
0x65, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x67,
0x72, 0x65, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x38, 0x0a, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74,
0x65, 0x64, 0x41, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f,
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d,
0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41,
0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28,
0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x4a,
0x04, 0x08, 0x06, 0x10, 0x07, 0x4a, 0x04, 0x08, 0x07, 0x10, 0x08, 0x22, 0xc8, 0x02, 0x0a, 0x0d,
0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a,
0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x26, 0x0a,
0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18,
0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74,
0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x30, 0x0a, 0x0a, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66,
0x69, 0x65, 0x72, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x63, 0x6f, 0x72, 0x65,
0x2e, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x52, 0x0a, 0x69, 0x64, 0x65,
0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75,
0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12,
0x34, 0x0a, 0x07, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b,
0x1c, 0x0a, 0x09, 0x61, 0x67, 0x72, 0x65, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01,
0x28, 0x09, 0x52, 0x09, 0x61, 0x67, 0x72, 0x65, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x38, 0x0a,
0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x65, 0x78,
0x70, 0x69, 0x72, 0x65, 0x73, 0x12, 0x2f, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e,
0x67, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x63, 0x6f, 0x72, 0x65,
0x2e, 0x43, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x52, 0x0a, 0x63, 0x68, 0x61, 0x6c,
0x6c, 0x65, 0x6e, 0x67, 0x65, 0x73, 0x12, 0x36, 0x0a, 0x16, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66,
0x69, 0x63, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65,
0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x16, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63,
0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x4a, 0x04,
0x08, 0x05, 0x10, 0x06, 0x4a, 0x04, 0x08, 0x07, 0x10, 0x08, 0x4a, 0x04, 0x08, 0x08, 0x10, 0x09,
0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x22, 0x93, 0x04, 0x0a, 0x05, 0x4f, 0x72, 0x64, 0x65, 0x72,
0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64,
0x12, 0x26, 0x0a, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74,
0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74,
0x75, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73,
0x12, 0x34, 0x0a, 0x07, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x65,
0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x12, 0x32, 0x0a, 0x0b, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69,
0x66, 0x69, 0x65, 0x72, 0x73, 0x18, 0x10, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x63, 0x6f,
0x72, 0x65, 0x2e, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x52, 0x0b, 0x69,
0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x12, 0x2a, 0x0a, 0x05, 0x65, 0x72,
0x72, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x63, 0x6f, 0x72, 0x65,
0x2e, 0x50, 0x72, 0x6f, 0x62, 0x6c, 0x65, 0x6d, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52,
0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x2a, 0x0a, 0x10, 0x76, 0x32, 0x41, 0x75, 0x74, 0x68,
0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x03,
0x52, 0x10, 0x76, 0x32, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f,
0x6e, 0x73, 0x12, 0x2c, 0x0a, 0x11, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74,
0x65, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x63,
0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c,
0x12, 0x34, 0x0a, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x63,
0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x12, 0x36, 0x0a, 0x16, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66,
0x69, 0x63, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65,
0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x16, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63,
0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1a,
0x0a, 0x08, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09,
0x52, 0x08, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x73, 0x12, 0x28, 0x0a, 0x0f, 0x62, 0x65,
0x67, 0x61, 0x6e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x18, 0x09, 0x20,
0x01, 0x28, 0x08, 0x52, 0x0f, 0x62, 0x65, 0x67, 0x61, 0x6e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73,
0x73, 0x69, 0x6e, 0x67, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x4a, 0x04, 0x08, 0x06, 0x10, 0x07,
0x4a, 0x04, 0x08, 0x0a, 0x10, 0x0b, 0x4a, 0x04, 0x08, 0x08, 0x10, 0x09, 0x22, 0x7a, 0x0a, 0x08,
0x43, 0x52, 0x4c, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x72, 0x69,
0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c,
0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05,
0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x38, 0x0a, 0x09, 0x72, 0x65, 0x76, 0x6f,
0x6b, 0x65, 0x64, 0x41, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f,
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69,
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x72, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x64,
0x41, 0x74, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x42, 0x2b, 0x5a, 0x29, 0x67, 0x69, 0x74, 0x68,
0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x65, 0x74, 0x73, 0x65, 0x6e, 0x63, 0x72, 0x79,
0x70, 0x74, 0x2f, 0x62, 0x6f, 0x75, 0x6c, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72,
0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75,
0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x4a,
0x04, 0x08, 0x03, 0x10, 0x04, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x4a, 0x04, 0x08, 0x06, 0x10,
0x07, 0x4a, 0x04, 0x08, 0x07, 0x10, 0x08, 0x22, 0xc8, 0x02, 0x0a, 0x0d, 0x41, 0x75, 0x74, 0x68,
0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x26, 0x0a, 0x0e, 0x72, 0x65, 0x67,
0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x03, 0x20, 0x01, 0x28,
0x03, 0x52, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49,
0x44, 0x12, 0x30, 0x0a, 0x0a, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18,
0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x49, 0x64, 0x65,
0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x52, 0x0a, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66,
0x69, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20,
0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x34, 0x0a, 0x07, 0x65,
0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67,
0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54,
0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65,
0x73, 0x12, 0x2f, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x73, 0x18,
0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x43, 0x68, 0x61,
0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x52, 0x0a, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67,
0x65, 0x73, 0x12, 0x36, 0x0a, 0x16, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74,
0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01,
0x28, 0x09, 0x52, 0x16, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x50,
0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x4a, 0x04, 0x08, 0x05, 0x10, 0x06,
0x4a, 0x04, 0x08, 0x07, 0x10, 0x08, 0x4a, 0x04, 0x08, 0x08, 0x10, 0x09, 0x4a, 0x04, 0x08, 0x02,
0x10, 0x03, 0x22, 0x93, 0x04, 0x0a, 0x05, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02,
0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x26, 0x0a, 0x0e,
0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x02,
0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69,
0x6f, 0x6e, 0x49, 0x44, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x07,
0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x34, 0x0a, 0x07,
0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x65, 0x78, 0x70, 0x69, 0x72,
0x65, 0x73, 0x12, 0x32, 0x0a, 0x0b, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72,
0x73, 0x18, 0x10, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x49,
0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x52, 0x0b, 0x69, 0x64, 0x65, 0x6e, 0x74,
0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x12, 0x2a, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18,
0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x72, 0x6f,
0x62, 0x6c, 0x65, 0x6d, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x05, 0x65, 0x72, 0x72,
0x6f, 0x72, 0x12, 0x2a, 0x0a, 0x10, 0x76, 0x32, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x03, 0x52, 0x10, 0x76, 0x32,
0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x2c,
0x0a, 0x11, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x53, 0x65, 0x72,
0x69, 0x61, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x63, 0x65, 0x72, 0x74, 0x69,
0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x12, 0x34, 0x0a, 0x07,
0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74,
0x65, 0x64, 0x12, 0x36, 0x0a, 0x16, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74,
0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x0e, 0x20, 0x01,
0x28, 0x09, 0x52, 0x16, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x50,
0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65,
0x70, 0x6c, 0x61, 0x63, 0x65, 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65,
0x70, 0x6c, 0x61, 0x63, 0x65, 0x73, 0x12, 0x28, 0x0a, 0x0f, 0x62, 0x65, 0x67, 0x61, 0x6e, 0x50,
0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52,
0x0f, 0x62, 0x65, 0x67, 0x61, 0x6e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x69, 0x6e, 0x67,
0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x4a, 0x04, 0x08, 0x06, 0x10, 0x07, 0x4a, 0x04, 0x08, 0x0a,
0x10, 0x0b, 0x4a, 0x04, 0x08, 0x08, 0x10, 0x09, 0x22, 0x7a, 0x0a, 0x08, 0x43, 0x52, 0x4c, 0x45,
0x6e, 0x74, 0x72, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x12, 0x16, 0x0a, 0x06,
0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x72, 0x65,
0x61, 0x73, 0x6f, 0x6e, 0x12, 0x38, 0x0a, 0x09, 0x72, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x64, 0x41,
0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74,
0x61, 0x6d, 0x70, 0x52, 0x09, 0x72, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x64, 0x41, 0x74, 0x4a, 0x04,
0x08, 0x03, 0x10, 0x04, 0x42, 0x2b, 0x5a, 0x29, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
0x6f, 0x6d, 0x2f, 0x6c, 0x65, 0x74, 0x73, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x2f, 0x62,
0x6f, 0x75, 0x6c, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
})
var (

View File

@ -83,7 +83,7 @@ message Registration {
// Next unused field number: 10
int64 id = 1;
bytes key = 2;
repeated string contact = 3;
reserved 3; // Previously contact
reserved 4; // Previously contactsPresent
string agreement = 5;
reserved 6; // Previously initialIP

View File

@ -123,7 +123,7 @@ func TestTableFromQuery(t *testing.T) {
expectedTable string
}{
{
query: "SELECT id, jwk, jwk_sha256, contact, agreement, createdAt, LockCol, status FROM registrations WHERE jwk_sha256 = ?",
query: "SELECT id, jwk, jwk_sha256, contact, agreement, createdAt, status FROM registrations WHERE jwk_sha256 = ?",
expectedTable: "registrations",
},
{
@ -135,11 +135,11 @@ func TestTableFromQuery(t *testing.T) {
expectedTable: "authz2",
},
{
query: "insert into `registrations` (`id`,`jwk`,`jw k_sha256`,`contact`,`agreement`,`createdAt`,`LockCol`,`status`) values (null,?,?,?,?,?,?,?,?);",
query: "insert into `registrations` (`id`,`jwk`,`jwk_sha256`,`contact`,`agreement`,`createdAt`,`status`) values (null,?,?,?,?,?,?,?);",
expectedTable: "`registrations`",
},
{
query: "update `registrations` set `jwk`=?, `jwk_sh a256`=?, `contact`=?, `agreement`=?, `createdAt`=?, `LockCol` =?, `status`=? where `id`=? and `LockCol`=?;",
query: "update `registrations` set `jwk`=?, `jwk_sha256`=?, `contact`=?, `agreement`=?, `createdAt`=?, `status`=? where `id`=?;",
expectedTable: "`registrations`",
},
{

View File

@ -79,7 +79,7 @@ services:
- setup
bmysql:
image: mariadb:10.6.22
image: mariadb:10.11.13
networks:
bouldernet:
aliases:

View File

@ -18,10 +18,12 @@ const (
// tokenPath is the path to the Salesforce OAuth2 token endpoint.
tokenPath = "/services/oauth2/token"
// contactsPath is the path to the Pardot v5 Prospects endpoint. This
// endpoint will create a new Prospect if one does not already exist with
// the same email address.
contactsPath = "/api/v5/objects/prospects"
// contactsPath is the path to the Pardot v5 Prospect upsert-by-email
// endpoint. This endpoint will create a new Prospect if one does not
// already exist with the same email address.
//
// https://developer.salesforce.com/docs/marketing/pardot/guide/prospect-v5.html#prospect-upsert-by-email
contactsPath = "/api/v5/objects/prospects/do/upsertLatestByEmail"
// maxAttempts is the maximum number of attempts to retry a request.
maxAttempts = 3
@ -60,7 +62,7 @@ type PardotClientImpl struct {
businessUnit string
clientId string
clientSecret string
contactsURL string
endpointURL string
tokenURL string
token *oAuthToken
clk clock.Clock
@ -70,7 +72,7 @@ var _ PardotClient = &PardotClientImpl{}
// NewPardotClientImpl creates a new PardotClientImpl.
func NewPardotClientImpl(clk clock.Clock, businessUnit, clientId, clientSecret, oauthbaseURL, pardotBaseURL string) (*PardotClientImpl, error) {
contactsURL, err := url.JoinPath(pardotBaseURL, contactsPath)
endpointURL, err := url.JoinPath(pardotBaseURL, contactsPath)
if err != nil {
return nil, fmt.Errorf("failed to join contacts path: %w", err)
}
@ -83,7 +85,7 @@ func NewPardotClientImpl(clk clock.Clock, businessUnit, clientId, clientSecret,
businessUnit: businessUnit,
clientId: clientId,
clientSecret: clientSecret,
contactsURL: contactsURL,
endpointURL: endpointURL,
tokenURL: tokenURL,
token: &oAuthToken{},
clk: clk,
@ -140,6 +142,19 @@ func redactEmail(body []byte, email string) string {
return string(bytes.ReplaceAll(body, []byte(email), []byte("[REDACTED]")))
}
type prospect struct {
// Email is the email address of the prospect.
Email string `json:"email"`
}
type upsertPayload struct {
// MatchEmail is the email address to match against existing prospects to
// avoid adding duplicates.
MatchEmail string `json:"matchEmail"`
// Prospect is the prospect data to be upserted.
Prospect prospect `json:"prospect"`
}
// SendContact submits an email to the Pardot Contacts endpoint, retrying up
// to 3 times with exponential backoff.
func (pc *PardotClientImpl) SendContact(email string) error {
@ -156,7 +171,10 @@ func (pc *PardotClientImpl) SendContact(email string) error {
return fmt.Errorf("failed to update token: %w", err)
}
payload, err := json.Marshal(map[string]string{"email": email})
payload, err := json.Marshal(upsertPayload{
MatchEmail: email,
Prospect: prospect{Email: email},
})
if err != nil {
return fmt.Errorf("failed to marshal payload: %w", err)
}
@ -165,7 +183,7 @@ func (pc *PardotClientImpl) SendContact(email string) error {
for attempt := range maxAttempts {
time.Sleep(core.RetryBackoff(attempt, retryBackoffMin, retryBackoffMax, retryBackoffBase))
req, err := http.NewRequest("POST", pc.contactsURL, bytes.NewReader(payload))
req, err := http.NewRequest("POST", pc.endpointURL, bytes.NewReader(payload))
if err != nil {
finalErr = fmt.Errorf("failed to create new contact request: %w", err)
continue

View File

@ -245,7 +245,7 @@ func CertificatesPerDomainError(retryAfter time.Duration, msg string, args ...an
func CertificatesPerFQDNSetError(retryAfter time.Duration, msg string, args ...any) error {
return &BoulderError{
Type: RateLimit,
Detail: fmt.Sprintf(msg+": see https://letsencrypt.org/docs/rate-limits/#new-certificates-per-exact-set-of-hostnames", args...),
Detail: fmt.Sprintf(msg+": see https://letsencrypt.org/docs/rate-limits/#new-certificates-per-exact-set-of-identifiers", args...),
RetryAfter: retryAfter,
}
}

6
go.mod
View File

@ -5,9 +5,9 @@ go 1.24.0
require (
github.com/aws/aws-sdk-go-v2 v1.36.5
github.com/aws/aws-sdk-go-v2/config v1.29.17
github.com/aws/aws-sdk-go-v2/service/s3 v1.81.0
github.com/aws/aws-sdk-go-v2/service/s3 v1.83.0
github.com/aws/smithy-go v1.22.4
github.com/eggsampler/acme/v3 v3.6.2-0.20250208073118-0466a0230941
github.com/eggsampler/acme/v3 v3.6.2
github.com/go-jose/go-jose/v4 v4.1.0
github.com/go-logr/stdr v1.2.2
github.com/go-sql-driver/mysql v1.9.1
@ -25,7 +25,7 @@ require (
github.com/prometheus/client_golang v1.22.0
github.com/prometheus/client_model v0.6.1
github.com/redis/go-redis/extra/redisotel/v9 v9.5.3
github.com/redis/go-redis/v9 v9.7.3
github.com/redis/go-redis/v9 v9.10.0
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399
github.com/weppos/publicsuffix-go v0.40.3-0.20250307081557-c05521c3453a
github.com/zmap/zcrypto v0.0.0-20250129210703-03c45d0bae98

12
go.sum
View File

@ -33,8 +33,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17 h1:t0E6FzRE
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17/go.mod h1:ygpklyoaypuyDvOM5ujWGrYWpAK3h7ugnmKCU/76Ys4=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17 h1:qcLWgdhq45sDM9na4cvXax9dyLitn8EYBRl8Ak4XtG4=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17/go.mod h1:M+jkjBFZ2J6DJrjMv2+vkBbuht6kxJYtJiwoVgX4p4U=
github.com/aws/aws-sdk-go-v2/service/s3 v1.81.0 h1:1GmCadhKR3J2sMVKs2bAYq9VnwYeCqfRyZzD4RASGlA=
github.com/aws/aws-sdk-go-v2/service/s3 v1.81.0/go.mod h1:kUklwasNoCn5YpyAqC/97r6dzTA1SRKJfKq16SXeoDU=
github.com/aws/aws-sdk-go-v2/service/s3 v1.83.0 h1:5Y75q0RPQoAbieyOuGLhjV9P3txvYgXv2lg0UwJOfmE=
github.com/aws/aws-sdk-go-v2/service/s3 v1.83.0/go.mod h1:kUklwasNoCn5YpyAqC/97r6dzTA1SRKJfKq16SXeoDU=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.5 h1:AIRJ3lfb2w/1/8wOOSqYb9fUKGwQbtysJ2H1MofRUPg=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.5/go.mod h1:b7SiVprpU+iGazDUqvRSLf5XmCdn+JtT1on7uNL6Ipc=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3 h1:BpOxT3yhLwSJ77qIY3DoHAQjZsc4HEGfMCE4NGy3uFg=
@ -70,8 +70,8 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/eggsampler/acme/v3 v3.6.2-0.20250208073118-0466a0230941 h1:CnQwymLMJ3MSfjbZQ/bpaLfuXBZuM3LUgAHJ0gO/7d8=
github.com/eggsampler/acme/v3 v3.6.2-0.20250208073118-0466a0230941/go.mod h1:/qh0rKC/Dh7Jj+p4So7DbWmFNzC4dpcpK53r226Fhuo=
github.com/eggsampler/acme/v3 v3.6.2 h1:gvyZbQ92wNQLDASVftGpHEdFwPSfg0+17P0lLt09Tp8=
github.com/eggsampler/acme/v3 v3.6.2/go.mod h1:/qh0rKC/Dh7Jj+p4So7DbWmFNzC4dpcpK53r226Fhuo=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
@ -214,8 +214,8 @@ github.com/redis/go-redis/extra/rediscmd/v9 v9.5.3 h1:1/BDligzCa40GTllkDnY3Y5DTH
github.com/redis/go-redis/extra/rediscmd/v9 v9.5.3/go.mod h1:3dZmcLn3Qw6FLlWASn1g4y+YO9ycEFUOM+bhBmzLVKQ=
github.com/redis/go-redis/extra/redisotel/v9 v9.5.3 h1:kuvuJL/+MZIEdvtb/kTBRiRgYaOmx1l+lYJyVdrRUOs=
github.com/redis/go-redis/extra/redisotel/v9 v9.5.3/go.mod h1:7f/FMrf5RRRVHXgfk7CzSVzXHiWeuOQUu2bsVqWoa+g=
github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM=
github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA=
github.com/redis/go-redis/v9 v9.10.0 h1:FxwK3eV8p/CQa0Ch276C7u2d0eNC9kCmAYQ7mCXCzVs=
github.com/redis/go-redis/v9 v9.10.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=

View File

@ -232,10 +232,6 @@ func RegistrationToPB(reg core.Registration) (*corepb.Registration, error) {
if err != nil {
return nil, err
}
var contacts []string
if reg.Contact != nil {
contacts = *reg.Contact
}
var createdAt *timestamppb.Timestamp
if reg.CreatedAt != nil {
createdAt = timestamppb.New(reg.CreatedAt.UTC())
@ -247,7 +243,6 @@ func RegistrationToPB(reg core.Registration) (*corepb.Registration, error) {
return &corepb.Registration{
Id: reg.ID,
Key: keyBytes,
Contact: contacts,
Agreement: reg.Agreement,
CreatedAt: createdAt,
Status: string(reg.Status),
@ -265,14 +260,9 @@ func PbToRegistration(pb *corepb.Registration) (core.Registration, error) {
c := pb.CreatedAt.AsTime()
createdAt = &c
}
var contacts *[]string
if len(pb.Contact) != 0 {
contacts = &pb.Contact
}
return core.Registration{
ID: pb.Id,
Key: &key,
Contact: contacts,
Agreement: pb.Agreement,
CreatedAt: createdAt,
Status: core.AcmeStatus(pb.Status),

View File

@ -167,7 +167,6 @@ func TestValidationResult(t *testing.T) {
}
func TestRegistration(t *testing.T) {
contacts := []string{"email"}
var key jose.JSONWebKey
err := json.Unmarshal([]byte(`
{
@ -181,7 +180,6 @@ func TestRegistration(t *testing.T) {
inReg := core.Registration{
ID: 1,
Key: &key,
Contact: &contacts,
Agreement: "yup",
CreatedAt: &createdAt,
Status: core.StatusValid,
@ -192,28 +190,9 @@ func TestRegistration(t *testing.T) {
test.AssertNotError(t, err, "PbToRegistration failed")
test.AssertDeepEquals(t, inReg, outReg)
inReg.Contact = nil
pbReg, err = RegistrationToPB(inReg)
test.AssertNotError(t, err, "registrationToPB failed")
pbReg.Contact = []string{}
outReg, err = PbToRegistration(pbReg)
test.AssertNotError(t, err, "PbToRegistration failed")
test.AssertDeepEquals(t, inReg, outReg)
var empty []string
inReg.Contact = &empty
pbReg, err = RegistrationToPB(inReg)
test.AssertNotError(t, err, "registrationToPB failed")
outReg, err = PbToRegistration(pbReg)
test.AssertNotError(t, err, "PbToRegistration failed")
if outReg.Contact != nil {
t.Errorf("Empty contacts should be a nil slice")
}
inRegNilCreatedAt := core.Registration{
ID: 1,
Key: &key,
Contact: &contacts,
Agreement: "yup",
CreatedAt: nil,
Status: core.StatusValid,

View File

@ -157,6 +157,8 @@ func parseReservedPrefixFile(registryData []byte, addressFamily string) ([]reser
// IsReservedAddr returns an error if an IP address is part of a reserved range.
func IsReservedAddr(ip netip.Addr) error {
// Strip zone from IPv6 addresses before checking
ip = ip.WithZone("")
for _, rpx := range reservedPrefixes {
if rpx.addressBlock.Contains(ip) {
return fmt.Errorf("IP address is in a reserved address block: %s: %s", rpx.rfc, rpx.name)

View File

@ -26,6 +26,9 @@ func TestIsReservedAddr(t *testing.T) {
{"febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "Link-Local Unicast"}, // highest IP in a reserved /10
{"fec0::1", ""}, // second-lowest IP just above a reserved /10
{"fe80::1%eth0", "Link-Local Unicast"}, // IPv6 link-local with zone
{"::1%lo", "Loopback Address"}, // IPv6 loopback with zone
{"192.0.0.170", "NAT64/DNS64 Discovery"}, // first of two reserved IPs that are comma-split in IANA's CSV; also a more-specific of a larger reserved block that comes first
{"192.0.0.171", "NAT64/DNS64 Discovery"}, // second of two reserved IPs that are comma-split in IANA's CSV; also a more-specific of a larger reserved block that comes first
{"2001:1::1", "Port Control Protocol Anycast"}, // reserved IP that comes after a line with a line break in IANA's CSV; also a more-specific of a larger reserved block that comes first

View File

@ -118,10 +118,28 @@ func NewIP(ip netip.Addr) ACMEIdentifier {
// RFC 8738, Sec. 3: The identifier value MUST contain the textual form
// of the address as defined in RFC 1123, Sec. 2.1 for IPv4 and in RFC
// 5952, Sec. 4 for IPv6.
Value: ip.String(),
Value: ip.WithZone("").String(),
}
}
// FromString converts a string to an ACMEIdentifier.
func FromString(identStr string) ACMEIdentifier {
ip, err := netip.ParseAddr(identStr)
if err == nil {
return NewIP(ip)
}
return NewDNS(identStr)
}
// FromStringSlice converts a slice of strings to a slice of ACMEIdentifier.
func FromStringSlice(identStrs []string) ACMEIdentifiers {
var idents ACMEIdentifiers
for _, identStr := range identStrs {
idents = append(idents, FromString(identStr))
}
return idents
}
// fromX509 extracts the Subject Alternative Names from a certificate or CSR's fields, and
// returns a slice of ACMEIdentifiers.
func fromX509(commonName string, dnsNames []string, ipAddresses []net.IP) ACMEIdentifiers {

View File

@ -10,6 +10,39 @@ import (
"testing"
)
func TestNewIP(t *testing.T) {
cases := []struct {
name string
ip netip.Addr
want ACMEIdentifier
}{
{
name: "IPv4 address",
ip: netip.MustParseAddr("9.9.9.9"),
want: ACMEIdentifier{Type: TypeIP, Value: "9.9.9.9"},
},
{
name: "IPv6 address",
ip: netip.MustParseAddr("fe80::cafe"),
want: ACMEIdentifier{Type: TypeIP, Value: "fe80::cafe"},
},
{
name: "IPv6 address with scope zone",
ip: netip.MustParseAddr("fe80::cafe%lo"),
want: ACMEIdentifier{Type: TypeIP, Value: "fe80::cafe"},
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
got := NewIP(tc.ip)
if got != tc.want {
t.Errorf("NewIP(%#v) = %#v, but want %#v", tc.ip, got, tc.want)
}
})
}
}
// TestFromX509 tests FromCert and FromCSR, which are fromX509's public
// wrappers.
func TestFromX509(t *testing.T) {

View File

@ -142,10 +142,10 @@ func (p *Profile) GenerateValidity(now time.Time) (time.Time, time.Time) {
// Don't use the full maxBackdate, to ensure that the actual backdate remains
// acceptable throughout the rest of the issuance process.
backdate := time.Duration(float64(p.maxBackdate.Nanoseconds()) * 0.9)
notBefore := now.Add(-1 * backdate)
notBefore := now.Add(-1 * backdate).Truncate(time.Second)
// Subtract one second, because certificate validity periods are *inclusive*
// of their final second (Baseline Requirements, Section 1.6.1).
notAfter := notBefore.Add(p.maxValidity).Add(-1 * time.Second)
notAfter := notBefore.Add(p.maxValidity).Add(-1 * time.Second).Truncate(time.Second)
return notBefore, notAfter
}

View File

@ -120,7 +120,7 @@ func TestLoadSigner(t *testing.T) {
{"invalid key file", IssuerLoc{File: "../test/hierarchy/int-e1.crl.pem"}, "unable to parse"},
{"ECDSA key file", IssuerLoc{File: "../test/hierarchy/int-e1.key.pem"}, ""},
{"RSA key file", IssuerLoc{File: "../test/hierarchy/int-r3.key.pem"}, ""},
{"invalid config file", IssuerLoc{ConfigFile: "../test/hostname-policy.yaml"}, "invalid character"},
{"invalid config file", IssuerLoc{ConfigFile: "../test/ident-policy.yaml"}, "invalid character"},
// Note that we don't have a test for "valid config file" because it would
// always fail -- in CI, the softhsm hasn't been initialized, so there's no
// key to look up; locally even if the softhsm has been initialized, the

View File

@ -77,7 +77,6 @@ func (sa *StorageAuthorityReadOnly) GetRegistration(_ context.Context, req *sapb
Id: req.Id,
Key: []byte(test1KeyPublicJSON),
Agreement: agreementURL,
Contact: []string{"mailto:person@mail.com"},
Status: string(core.StatusValid),
}
@ -131,14 +130,11 @@ func (sa *StorageAuthorityReadOnly) GetRegistrationByKey(_ context.Context, req
return nil, err
}
contacts := []string{"mailto:person@mail.com"}
if bytes.Equal(req.Jwk, []byte(test1KeyPublicJSON)) {
return &corepb.Registration{
Id: 1,
Key: req.Jwk,
Agreement: agreementURL,
Contact: contacts,
Status: string(core.StatusValid),
}, nil
}
@ -172,7 +168,6 @@ func (sa *StorageAuthorityReadOnly) GetRegistrationByKey(_ context.Context, req
Id: 2,
Key: req.Jwk,
Agreement: agreementURL,
Contact: contacts,
Status: string(core.StatusDeactivated),
}, nil
}

View File

@ -28,10 +28,11 @@ import (
type AuthorityImpl struct {
log blog.Logger
blocklist map[string]bool
exactBlocklist map[string]bool
wildcardExactBlocklist map[string]bool
blocklistMu sync.RWMutex
domainBlocklist map[string]bool
fqdnBlocklist map[string]bool
wildcardFqdnBlocklist map[string]bool
ipPrefixBlocklist []netip.Prefix
blocklistMu sync.RWMutex
enabledChallenges map[core.AcmeChallenge]bool
enabledIdentifiers map[identifier.IdentifierType]bool
@ -46,56 +47,61 @@ func New(identifierTypes map[identifier.IdentifierType]bool, challengeTypes map[
}, nil
}
// blockedNamesPolicy is a struct holding lists of blocked domain names. One for
// exact blocks and one for blocks including all subdomains.
type blockedNamesPolicy struct {
// ExactBlockedNames is a list of domain names. Issuance for names exactly
// matching an entry in the list will be forbidden. (e.g. `ExactBlockedNames`
// containing `www.example.com` will not block `example.com` or
// `mail.example.com`).
// blockedIdentsPolicy is a struct holding lists of blocked identifiers.
type blockedIdentsPolicy struct {
// ExactBlockedNames is a list of Fully Qualified Domain Names (FQDNs).
// Issuance for names exactly matching an entry in the list will be
// forbidden. (e.g. `ExactBlockedNames` containing `www.example.com` will
// not block `example.com`, `mail.example.com`, or `dev.www.example.com`).
ExactBlockedNames []string `yaml:"ExactBlockedNames"`
// HighRiskBlockedNames is like ExactBlockedNames except that issuance is
// blocked for subdomains as well. (e.g. BlockedNames containing `example.com`
// will block `www.example.com`).
// HighRiskBlockedNames is a list of domain names: like ExactBlockedNames
// except that issuance is blocked for subdomains as well. (e.g.
// BlockedNames containing `example.com` will block `www.example.com`).
//
// This list typically doesn't change with much regularity.
HighRiskBlockedNames []string `yaml:"HighRiskBlockedNames"`
// AdminBlockedNames operates the same as BlockedNames but is changed with more
// frequency based on administrative blocks/revocations that are added over
// time above and beyond the high-risk domains. Managing these entries separately
// from HighRiskBlockedNames makes it easier to vet changes accurately.
// AdminBlockedNames operates the same as HighRiskBlockedNames but is
// changed with more frequency based on administrative blocks/revocations
// that are added over time above and beyond the high-risk domains. Managing
// these entries separately from HighRiskBlockedNames makes it easier to vet
// changes accurately.
AdminBlockedNames []string `yaml:"AdminBlockedNames"`
// AdminBlockedPrefixes is a list of IP address prefixes. All IP addresses
// contained within the prefix are blocked.
AdminBlockedPrefixes []string `yaml:"AdminBlockedPrefixes"`
}
// LoadHostnamePolicyFile will load the given policy file, returning an error if
// it fails.
func (pa *AuthorityImpl) LoadHostnamePolicyFile(f string) error {
// LoadIdentPolicyFile will load the given policy file, returning an error if it
// fails.
func (pa *AuthorityImpl) LoadIdentPolicyFile(f string) error {
configBytes, err := os.ReadFile(f)
if err != nil {
return err
}
hash := sha256.Sum256(configBytes)
pa.log.Infof("loading hostname policy, sha256: %s", hex.EncodeToString(hash[:]))
var policy blockedNamesPolicy
pa.log.Infof("loading identifier policy, sha256: %s", hex.EncodeToString(hash[:]))
var policy blockedIdentsPolicy
err = strictyaml.Unmarshal(configBytes, &policy)
if err != nil {
return err
}
if len(policy.HighRiskBlockedNames) == 0 {
return fmt.Errorf("No entries in HighRiskBlockedNames.")
return fmt.Errorf("no entries in HighRiskBlockedNames")
}
if len(policy.ExactBlockedNames) == 0 {
return fmt.Errorf("No entries in ExactBlockedNames.")
return fmt.Errorf("no entries in ExactBlockedNames")
}
return pa.processHostnamePolicy(policy)
return pa.processIdentPolicy(policy)
}
// processHostnamePolicy handles loading a new blockedNamesPolicy into the PA.
// All of the policy.ExactBlockedNames will be added to the
// wildcardExactBlocklist by processHostnamePolicy to ensure that wildcards for
// exact blocked names entries are forbidden.
func (pa *AuthorityImpl) processHostnamePolicy(policy blockedNamesPolicy) error {
// processIdentPolicy handles loading a new blockedIdentsPolicy into the PA. All
// of the policy.ExactBlockedNames will be added to the wildcardExactBlocklist
// by processIdentPolicy to ensure that wildcards for exact blocked names
// entries are forbidden.
func (pa *AuthorityImpl) processIdentPolicy(policy blockedIdentsPolicy) error {
nameMap := make(map[string]bool)
for _, v := range policy.HighRiskBlockedNames {
nameMap[v] = true
@ -103,6 +109,7 @@ func (pa *AuthorityImpl) processHostnamePolicy(policy blockedNamesPolicy) error
for _, v := range policy.AdminBlockedNames {
nameMap[v] = true
}
exactNameMap := make(map[string]bool)
wildcardNameMap := make(map[string]bool)
for _, v := range policy.ExactBlockedNames {
@ -119,16 +126,28 @@ func (pa *AuthorityImpl) processHostnamePolicy(policy blockedNamesPolicy) error
// at least be a "something." and a TLD like "com"
if len(parts) < 2 {
return fmt.Errorf(
"Malformed ExactBlockedNames entry, only one label: %q", v)
"malformed ExactBlockedNames entry, only one label: %q", v)
}
// Add the second part, the domain minus the first label, to the
// wildcardNameMap to block issuance for `*.`+parts[1]
wildcardNameMap[parts[1]] = true
}
var prefixes []netip.Prefix
for _, p := range policy.AdminBlockedPrefixes {
prefix, err := netip.ParsePrefix(p)
if err != nil {
return fmt.Errorf(
"malformed AdminBlockedPrefixes entry, not a prefix: %q", p)
}
prefixes = append(prefixes, prefix)
}
pa.blocklistMu.Lock()
pa.blocklist = nameMap
pa.exactBlocklist = exactNameMap
pa.wildcardExactBlocklist = wildcardNameMap
pa.domainBlocklist = nameMap
pa.fqdnBlocklist = exactNameMap
pa.wildcardFqdnBlocklist = wildcardNameMap
pa.ipPrefixBlocklist = prefixes
pa.blocklistMu.Unlock()
return nil
}
@ -326,6 +345,7 @@ func ValidDomain(domain string) error {
// ValidIP checks that an IP address:
// - isn't empty
// - is an IPv4 or IPv6 address
// - doesn't contain a scope zone (RFC 4007)
// - isn't in an IANA special-purpose address registry
//
// It does NOT ensure that the IP address is absent from any PA blocked lists.
@ -340,7 +360,7 @@ func ValidIP(ip string) error {
// 5952, Sec. 4 for IPv6.") ParseAddr() will accept a non-compliant but
// otherwise valid string; String() will output a compliant string.
parsedIP, err := netip.ParseAddr(ip)
if err != nil || parsedIP.String() != ip {
if err != nil || parsedIP.WithZone("").String() != ip {
return errIPInvalid
}
@ -423,32 +443,26 @@ func (pa *AuthorityImpl) WillingToIssue(idents identifier.ACMEIdentifiers) error
continue
}
// Only DNS identifiers are subject to wildcard and blocklist checks.
// Unsupported identifier types will have been caught by
// WellFormedIdentifiers().
//
// TODO(#8237): We may want to implement IP address blocklists too.
if ident.Type == identifier.TypeDNS {
if strings.Count(ident.Value, "*") > 0 {
// The base domain is the wildcard request with the `*.` prefix removed
baseDomain := strings.TrimPrefix(ident.Value, "*.")
// Wildcard DNS identifiers are checked against an additional blocklist.
if ident.Type == identifier.TypeDNS && strings.Count(ident.Value, "*") > 0 {
// The base domain is the wildcard request with the `*.` prefix removed
baseDomain := strings.TrimPrefix(ident.Value, "*.")
// The base domain can't be in the wildcard exact blocklist
err = pa.checkWildcardHostList(baseDomain)
if err != nil {
subErrors = append(subErrors, subError(ident, err))
continue
}
}
// For both wildcard and non-wildcard domains, check whether any parent domain
// name is on the regular blocklist.
err := pa.checkHostLists(ident.Value)
// The base domain can't be in the wildcard exact blocklist
err = pa.checkWildcardBlocklist(baseDomain)
if err != nil {
subErrors = append(subErrors, subError(ident, err))
continue
}
}
// For all identifier types, check whether the identifier value is
// covered by the regular blocklists.
err := pa.checkBlocklists(ident)
if err != nil {
subErrors = append(subErrors, subError(ident, err))
continue
}
}
return combineSubErrors(subErrors)
}
@ -477,6 +491,7 @@ func (pa *AuthorityImpl) WillingToIssue(idents identifier.ACMEIdentifiers) error
//
// For IP identifiers:
// - MUST match the syntax of an IP address
// - MUST NOT contain a scope zone (RFC 4007)
// - MUST NOT be in an IANA special-purpose address registry
//
// If multiple identifiers are invalid, the error will contain suberrors
@ -528,42 +543,57 @@ func combineSubErrors(subErrors []berrors.SubBoulderError) error {
return nil
}
// checkWildcardHostList checks the wildcardExactBlocklist for a given domain.
// checkWildcardBlocklist checks the wildcardExactBlocklist for a given domain.
// If the domain is not present on the list nil is returned, otherwise
// errPolicyForbidden is returned.
func (pa *AuthorityImpl) checkWildcardHostList(domain string) error {
func (pa *AuthorityImpl) checkWildcardBlocklist(domain string) error {
pa.blocklistMu.RLock()
defer pa.blocklistMu.RUnlock()
if pa.wildcardExactBlocklist == nil {
return fmt.Errorf("Hostname policy not yet loaded.")
if pa.wildcardFqdnBlocklist == nil {
return fmt.Errorf("identifier policy not yet loaded")
}
if pa.wildcardExactBlocklist[domain] {
if pa.wildcardFqdnBlocklist[domain] {
return errPolicyForbidden
}
return nil
}
func (pa *AuthorityImpl) checkHostLists(domain string) error {
func (pa *AuthorityImpl) checkBlocklists(ident identifier.ACMEIdentifier) error {
pa.blocklistMu.RLock()
defer pa.blocklistMu.RUnlock()
if pa.blocklist == nil {
return fmt.Errorf("Hostname policy not yet loaded.")
if pa.domainBlocklist == nil {
return fmt.Errorf("identifier policy not yet loaded")
}
labels := strings.Split(domain, ".")
for i := range labels {
joined := strings.Join(labels[i:], ".")
if pa.blocklist[joined] {
switch ident.Type {
case identifier.TypeDNS:
labels := strings.Split(ident.Value, ".")
for i := range labels {
joined := strings.Join(labels[i:], ".")
if pa.domainBlocklist[joined] {
return errPolicyForbidden
}
}
if pa.fqdnBlocklist[ident.Value] {
return errPolicyForbidden
}
}
if pa.exactBlocklist[domain] {
return errPolicyForbidden
case identifier.TypeIP:
ip, err := netip.ParseAddr(ident.Value)
if err != nil {
return errIPInvalid
}
for _, prefix := range pa.ipPrefixBlocklist {
if prefix.Contains(ip.WithZone("")) {
return errPolicyForbidden
}
}
default:
return errUnsupportedIdent
}
return nil
}

View File

@ -136,6 +136,8 @@ func TestWellFormedIdentifiers(t *testing.T) {
{identifier.ACMEIdentifier{Type: "ip", Value: `1.1.168.192.in-addr.arpa`}, errIPInvalid}, // reverse DNS
// Unexpected IPv6 variants
{identifier.ACMEIdentifier{Type: "ip", Value: `2602:80a:6000:abad:cafe::1%lo`}, errIPInvalid}, // scope zone (RFC 4007)
{identifier.ACMEIdentifier{Type: "ip", Value: `2602:80a:6000:abad:cafe::1%`}, errIPInvalid}, // empty scope zone (RFC 4007)
{identifier.ACMEIdentifier{Type: "ip", Value: `3fff:aaa:a:c0ff:ee:a:bad:deed:ffff`}, errIPInvalid}, // extra octet
{identifier.ACMEIdentifier{Type: "ip", Value: `3fff:aaa:a:c0ff:ee:a:bad:mead`}, errIPInvalid}, // character out of range
{identifier.ACMEIdentifier{Type: "ip", Value: `2001:db8::/32`}, errIPInvalid}, // with CIDR
@ -178,6 +180,9 @@ func TestWillingToIssue(t *testing.T) {
identifier.NewDNS(`lots.of.labels.website4.com`),
identifier.NewDNS(`banned.in.dc.com`),
identifier.NewDNS(`bad.brains.banned.in.dc.com`),
identifier.NewIP(netip.MustParseAddr(`64.112.117.66`)),
identifier.NewIP(netip.MustParseAddr(`2602:80a:6000:666::1`)),
identifier.NewIP(netip.MustParseAddr(`2602:80a:6000:666::1%lo`)),
}
blocklistContents := []string{
`website2.com`,
@ -191,9 +196,13 @@ func TestWillingToIssue(t *testing.T) {
`highvalue.website1.org`,
`dl.website1.org`,
}
adminBlockedContents := []string{
adminBlockedNamesContents := []string{
`banned.in.dc.com`,
}
adminBlockedPrefixesContents := []string{
`64.112.117.66/32`,
`2602:80a:6000:666::/64`,
}
shouldBeAccepted := identifier.ACMEIdentifiers{
identifier.NewDNS(`lowvalue.website1.org`),
@ -204,14 +213,17 @@ func TestWillingToIssue(t *testing.T) {
identifier.NewDNS(`8675309.com`),
identifier.NewDNS(`web5ite2.com`),
identifier.NewDNS(`www.web-site2.com`),
identifier.NewIP(netip.MustParseAddr(`9.9.9.9`)),
identifier.NewDNS(`www.highvalue.website1.org`),
identifier.NewIP(netip.MustParseAddr(`64.112.117.67`)),
identifier.NewIP(netip.MustParseAddr(`2620:fe::fe`)),
identifier.NewIP(netip.MustParseAddr(`2602:80a:6000:667::`)),
}
policy := blockedNamesPolicy{
policy := blockedIdentsPolicy{
HighRiskBlockedNames: blocklistContents,
ExactBlockedNames: exactBlocklistContents,
AdminBlockedNames: adminBlockedContents,
AdminBlockedNames: adminBlockedNamesContents,
AdminBlockedPrefixes: adminBlockedPrefixesContents,
}
yamlPolicyBytes, err := yaml.Marshal(policy)
@ -223,7 +235,7 @@ func TestWillingToIssue(t *testing.T) {
pa := paImpl(t)
err = pa.LoadHostnamePolicyFile(yamlPolicyFile.Name())
err = pa.LoadIdentPolicyFile(yamlPolicyFile.Name())
test.AssertNotError(t, err, "Couldn't load rules")
// Invalid encoding
@ -240,7 +252,7 @@ func TestWillingToIssue(t *testing.T) {
test.AssertNotError(t, err, "WillingToIssue failed on a properly formed domain with IDN TLD")
features.Reset()
// Test expected blocked domains
// Test expected blocked identifiers
for _, ident := range shouldBeBlocked {
err := pa.WillingToIssue(identifier.ACMEIdentifiers{ident})
test.AssertError(t, err, "identifier was not correctly forbidden")
@ -249,7 +261,7 @@ func TestWillingToIssue(t *testing.T) {
test.AssertContains(t, berr.Detail, errPolicyForbidden.Error())
}
// Test acceptance of good names
// Test acceptance of good identifiers
for _, ident := range shouldBeAccepted {
err := pa.WillingToIssue(identifier.ACMEIdentifiers{ident})
test.AssertNotError(t, err, "identifier was incorrectly forbidden")
@ -265,7 +277,7 @@ func TestWillingToIssue_Wildcards(t *testing.T) {
}
pa := paImpl(t)
bannedBytes, err := yaml.Marshal(blockedNamesPolicy{
bannedBytes, err := yaml.Marshal(blockedIdentsPolicy{
HighRiskBlockedNames: bannedDomains,
ExactBlockedNames: exactBannedDomains,
})
@ -274,7 +286,7 @@ func TestWillingToIssue_Wildcards(t *testing.T) {
defer os.Remove(f.Name())
err = os.WriteFile(f.Name(), bannedBytes, 0640)
test.AssertNotError(t, err, "Couldn't write serialized banned list to file")
err = pa.LoadHostnamePolicyFile(f.Name())
err = pa.LoadIdentPolicyFile(f.Name())
test.AssertNotError(t, err, "Couldn't load policy contents from file")
testCases := []struct {
@ -359,7 +371,7 @@ func TestWillingToIssue_SubErrors(t *testing.T) {
}
pa := paImpl(t)
bannedBytes, err := yaml.Marshal(blockedNamesPolicy{
bannedBytes, err := yaml.Marshal(blockedIdentsPolicy{
HighRiskBlockedNames: banned,
ExactBlockedNames: banned,
})
@ -368,7 +380,7 @@ func TestWillingToIssue_SubErrors(t *testing.T) {
defer os.Remove(f.Name())
err = os.WriteFile(f.Name(), bannedBytes, 0640)
test.AssertNotError(t, err, "Couldn't write serialized banned list to file")
err = pa.LoadHostnamePolicyFile(f.Name())
err = pa.LoadIdentPolicyFile(f.Name())
test.AssertNotError(t, err, "Couldn't load policy contents from file")
// Test multiple malformed domains and one banned domain; only the malformed ones will generate errors
@ -511,7 +523,7 @@ func TestMalformedExactBlocklist(t *testing.T) {
}
// Create YAML for the exactBannedDomains
bannedBytes, err := yaml.Marshal(blockedNamesPolicy{
bannedBytes, err := yaml.Marshal(blockedIdentsPolicy{
HighRiskBlockedNames: bannedDomains,
ExactBlockedNames: exactBannedDomains,
})
@ -524,11 +536,11 @@ func TestMalformedExactBlocklist(t *testing.T) {
err = os.WriteFile(f.Name(), bannedBytes, 0640)
test.AssertNotError(t, err, "Couldn't write serialized banned list to file")
// Try to use the YAML tempfile as the hostname policy. It should produce an
// Try to use the YAML tempfile as the ident policy. It should produce an
// error since the exact blocklist contents are malformed.
err = pa.LoadHostnamePolicyFile(f.Name())
err = pa.LoadIdentPolicyFile(f.Name())
test.AssertError(t, err, "Loaded invalid exact blocklist content without error")
test.AssertEquals(t, err.Error(), "Malformed ExactBlockedNames entry, only one label: \"com\"")
test.AssertEquals(t, err.Error(), "malformed ExactBlockedNames entry, only one label: \"com\"")
}
func TestValidEmailError(t *testing.T) {
@ -694,7 +706,7 @@ func TestWillingToIssue_IdentifierType(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
policy := blockedNamesPolicy{
policy := blockedIdentsPolicy{
HighRiskBlockedNames: []string{"zombo.gov.us"},
ExactBlockedNames: []string{`highvalue.website1.org`},
AdminBlockedNames: []string{`banned.in.dc.com`},
@ -709,7 +721,7 @@ func TestWillingToIssue_IdentifierType(t *testing.T) {
pa := paImpl(t)
err = pa.LoadHostnamePolicyFile(yamlPolicyFile.Name())
err = pa.LoadIdentPolicyFile(yamlPolicyFile.Name())
test.AssertNotError(t, err, "Couldn't load rules")
pa.enabledIdentifiers = tc.enabled

View File

@ -157,58 +157,6 @@ func (x *GenerateOCSPRequest) GetSerial() string {
return ""
}
type UpdateRegistrationContactRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
RegistrationID int64 `protobuf:"varint,1,opt,name=registrationID,proto3" json:"registrationID,omitempty"`
Contacts []string `protobuf:"bytes,2,rep,name=contacts,proto3" json:"contacts,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *UpdateRegistrationContactRequest) Reset() {
*x = UpdateRegistrationContactRequest{}
mi := &file_ra_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *UpdateRegistrationContactRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*UpdateRegistrationContactRequest) ProtoMessage() {}
func (x *UpdateRegistrationContactRequest) ProtoReflect() protoreflect.Message {
mi := &file_ra_proto_msgTypes[3]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use UpdateRegistrationContactRequest.ProtoReflect.Descriptor instead.
func (*UpdateRegistrationContactRequest) Descriptor() ([]byte, []int) {
return file_ra_proto_rawDescGZIP(), []int{3}
}
func (x *UpdateRegistrationContactRequest) GetRegistrationID() int64 {
if x != nil {
return x.RegistrationID
}
return 0
}
func (x *UpdateRegistrationContactRequest) GetContacts() []string {
if x != nil {
return x.Contacts
}
return nil
}
type UpdateRegistrationKeyRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
RegistrationID int64 `protobuf:"varint,1,opt,name=registrationID,proto3" json:"registrationID,omitempty"`
@ -219,7 +167,7 @@ type UpdateRegistrationKeyRequest struct {
func (x *UpdateRegistrationKeyRequest) Reset() {
*x = UpdateRegistrationKeyRequest{}
mi := &file_ra_proto_msgTypes[4]
mi := &file_ra_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -231,7 +179,7 @@ func (x *UpdateRegistrationKeyRequest) String() string {
func (*UpdateRegistrationKeyRequest) ProtoMessage() {}
func (x *UpdateRegistrationKeyRequest) ProtoReflect() protoreflect.Message {
mi := &file_ra_proto_msgTypes[4]
mi := &file_ra_proto_msgTypes[3]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -244,7 +192,7 @@ func (x *UpdateRegistrationKeyRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use UpdateRegistrationKeyRequest.ProtoReflect.Descriptor instead.
func (*UpdateRegistrationKeyRequest) Descriptor() ([]byte, []int) {
return file_ra_proto_rawDescGZIP(), []int{4}
return file_ra_proto_rawDescGZIP(), []int{3}
}
func (x *UpdateRegistrationKeyRequest) GetRegistrationID() int64 {
@ -270,7 +218,7 @@ type DeactivateRegistrationRequest struct {
func (x *DeactivateRegistrationRequest) Reset() {
*x = DeactivateRegistrationRequest{}
mi := &file_ra_proto_msgTypes[5]
mi := &file_ra_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -282,7 +230,7 @@ func (x *DeactivateRegistrationRequest) String() string {
func (*DeactivateRegistrationRequest) ProtoMessage() {}
func (x *DeactivateRegistrationRequest) ProtoReflect() protoreflect.Message {
mi := &file_ra_proto_msgTypes[5]
mi := &file_ra_proto_msgTypes[4]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -295,7 +243,7 @@ func (x *DeactivateRegistrationRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use DeactivateRegistrationRequest.ProtoReflect.Descriptor instead.
func (*DeactivateRegistrationRequest) Descriptor() ([]byte, []int) {
return file_ra_proto_rawDescGZIP(), []int{5}
return file_ra_proto_rawDescGZIP(), []int{4}
}
func (x *DeactivateRegistrationRequest) GetRegistrationID() int64 {
@ -316,7 +264,7 @@ type UpdateAuthorizationRequest struct {
func (x *UpdateAuthorizationRequest) Reset() {
*x = UpdateAuthorizationRequest{}
mi := &file_ra_proto_msgTypes[6]
mi := &file_ra_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -328,7 +276,7 @@ func (x *UpdateAuthorizationRequest) String() string {
func (*UpdateAuthorizationRequest) ProtoMessage() {}
func (x *UpdateAuthorizationRequest) ProtoReflect() protoreflect.Message {
mi := &file_ra_proto_msgTypes[6]
mi := &file_ra_proto_msgTypes[5]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -341,7 +289,7 @@ func (x *UpdateAuthorizationRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use UpdateAuthorizationRequest.ProtoReflect.Descriptor instead.
func (*UpdateAuthorizationRequest) Descriptor() ([]byte, []int) {
return file_ra_proto_rawDescGZIP(), []int{6}
return file_ra_proto_rawDescGZIP(), []int{5}
}
func (x *UpdateAuthorizationRequest) GetAuthz() *proto.Authorization {
@ -375,7 +323,7 @@ type PerformValidationRequest struct {
func (x *PerformValidationRequest) Reset() {
*x = PerformValidationRequest{}
mi := &file_ra_proto_msgTypes[7]
mi := &file_ra_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -387,7 +335,7 @@ func (x *PerformValidationRequest) String() string {
func (*PerformValidationRequest) ProtoMessage() {}
func (x *PerformValidationRequest) ProtoReflect() protoreflect.Message {
mi := &file_ra_proto_msgTypes[7]
mi := &file_ra_proto_msgTypes[6]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -400,7 +348,7 @@ func (x *PerformValidationRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use PerformValidationRequest.ProtoReflect.Descriptor instead.
func (*PerformValidationRequest) Descriptor() ([]byte, []int) {
return file_ra_proto_rawDescGZIP(), []int{7}
return file_ra_proto_rawDescGZIP(), []int{6}
}
func (x *PerformValidationRequest) GetAuthz() *proto.Authorization {
@ -428,7 +376,7 @@ type RevokeCertByApplicantRequest struct {
func (x *RevokeCertByApplicantRequest) Reset() {
*x = RevokeCertByApplicantRequest{}
mi := &file_ra_proto_msgTypes[8]
mi := &file_ra_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -440,7 +388,7 @@ func (x *RevokeCertByApplicantRequest) String() string {
func (*RevokeCertByApplicantRequest) ProtoMessage() {}
func (x *RevokeCertByApplicantRequest) ProtoReflect() protoreflect.Message {
mi := &file_ra_proto_msgTypes[8]
mi := &file_ra_proto_msgTypes[7]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -453,7 +401,7 @@ func (x *RevokeCertByApplicantRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use RevokeCertByApplicantRequest.ProtoReflect.Descriptor instead.
func (*RevokeCertByApplicantRequest) Descriptor() ([]byte, []int) {
return file_ra_proto_rawDescGZIP(), []int{8}
return file_ra_proto_rawDescGZIP(), []int{7}
}
func (x *RevokeCertByApplicantRequest) GetCert() []byte {
@ -486,7 +434,7 @@ type RevokeCertByKeyRequest struct {
func (x *RevokeCertByKeyRequest) Reset() {
*x = RevokeCertByKeyRequest{}
mi := &file_ra_proto_msgTypes[9]
mi := &file_ra_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -498,7 +446,7 @@ func (x *RevokeCertByKeyRequest) String() string {
func (*RevokeCertByKeyRequest) ProtoMessage() {}
func (x *RevokeCertByKeyRequest) ProtoReflect() protoreflect.Message {
mi := &file_ra_proto_msgTypes[9]
mi := &file_ra_proto_msgTypes[8]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -511,7 +459,7 @@ func (x *RevokeCertByKeyRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use RevokeCertByKeyRequest.ProtoReflect.Descriptor instead.
func (*RevokeCertByKeyRequest) Descriptor() ([]byte, []int) {
return file_ra_proto_rawDescGZIP(), []int{9}
return file_ra_proto_rawDescGZIP(), []int{8}
}
func (x *RevokeCertByKeyRequest) GetCert() []byte {
@ -548,7 +496,7 @@ type AdministrativelyRevokeCertificateRequest struct {
func (x *AdministrativelyRevokeCertificateRequest) Reset() {
*x = AdministrativelyRevokeCertificateRequest{}
mi := &file_ra_proto_msgTypes[10]
mi := &file_ra_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -560,7 +508,7 @@ func (x *AdministrativelyRevokeCertificateRequest) String() string {
func (*AdministrativelyRevokeCertificateRequest) ProtoMessage() {}
func (x *AdministrativelyRevokeCertificateRequest) ProtoReflect() protoreflect.Message {
mi := &file_ra_proto_msgTypes[10]
mi := &file_ra_proto_msgTypes[9]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -573,7 +521,7 @@ func (x *AdministrativelyRevokeCertificateRequest) ProtoReflect() protoreflect.M
// Deprecated: Use AdministrativelyRevokeCertificateRequest.ProtoReflect.Descriptor instead.
func (*AdministrativelyRevokeCertificateRequest) Descriptor() ([]byte, []int) {
return file_ra_proto_rawDescGZIP(), []int{10}
return file_ra_proto_rawDescGZIP(), []int{9}
}
func (x *AdministrativelyRevokeCertificateRequest) GetCert() []byte {
@ -641,7 +589,7 @@ type NewOrderRequest struct {
func (x *NewOrderRequest) Reset() {
*x = NewOrderRequest{}
mi := &file_ra_proto_msgTypes[11]
mi := &file_ra_proto_msgTypes[10]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -653,7 +601,7 @@ func (x *NewOrderRequest) String() string {
func (*NewOrderRequest) ProtoMessage() {}
func (x *NewOrderRequest) ProtoReflect() protoreflect.Message {
mi := &file_ra_proto_msgTypes[11]
mi := &file_ra_proto_msgTypes[10]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -666,7 +614,7 @@ func (x *NewOrderRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use NewOrderRequest.ProtoReflect.Descriptor instead.
func (*NewOrderRequest) Descriptor() ([]byte, []int) {
return file_ra_proto_rawDescGZIP(), []int{11}
return file_ra_proto_rawDescGZIP(), []int{10}
}
func (x *NewOrderRequest) GetRegistrationID() int64 {
@ -713,7 +661,7 @@ type GetAuthorizationRequest struct {
func (x *GetAuthorizationRequest) Reset() {
*x = GetAuthorizationRequest{}
mi := &file_ra_proto_msgTypes[12]
mi := &file_ra_proto_msgTypes[11]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -725,7 +673,7 @@ func (x *GetAuthorizationRequest) String() string {
func (*GetAuthorizationRequest) ProtoMessage() {}
func (x *GetAuthorizationRequest) ProtoReflect() protoreflect.Message {
mi := &file_ra_proto_msgTypes[12]
mi := &file_ra_proto_msgTypes[11]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -738,7 +686,7 @@ func (x *GetAuthorizationRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetAuthorizationRequest.ProtoReflect.Descriptor instead.
func (*GetAuthorizationRequest) Descriptor() ([]byte, []int) {
return file_ra_proto_rawDescGZIP(), []int{12}
return file_ra_proto_rawDescGZIP(), []int{11}
}
func (x *GetAuthorizationRequest) GetId() int64 {
@ -758,7 +706,7 @@ type FinalizeOrderRequest struct {
func (x *FinalizeOrderRequest) Reset() {
*x = FinalizeOrderRequest{}
mi := &file_ra_proto_msgTypes[13]
mi := &file_ra_proto_msgTypes[12]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -770,7 +718,7 @@ func (x *FinalizeOrderRequest) String() string {
func (*FinalizeOrderRequest) ProtoMessage() {}
func (x *FinalizeOrderRequest) ProtoReflect() protoreflect.Message {
mi := &file_ra_proto_msgTypes[13]
mi := &file_ra_proto_msgTypes[12]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -783,7 +731,7 @@ func (x *FinalizeOrderRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use FinalizeOrderRequest.ProtoReflect.Descriptor instead.
func (*FinalizeOrderRequest) Descriptor() ([]byte, []int) {
return file_ra_proto_rawDescGZIP(), []int{13}
return file_ra_proto_rawDescGZIP(), []int{12}
}
func (x *FinalizeOrderRequest) GetOrder() *proto.Order {
@ -810,7 +758,7 @@ type UnpauseAccountRequest struct {
func (x *UnpauseAccountRequest) Reset() {
*x = UnpauseAccountRequest{}
mi := &file_ra_proto_msgTypes[14]
mi := &file_ra_proto_msgTypes[13]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -822,7 +770,7 @@ func (x *UnpauseAccountRequest) String() string {
func (*UnpauseAccountRequest) ProtoMessage() {}
func (x *UnpauseAccountRequest) ProtoReflect() protoreflect.Message {
mi := &file_ra_proto_msgTypes[14]
mi := &file_ra_proto_msgTypes[13]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -835,7 +783,7 @@ func (x *UnpauseAccountRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use UnpauseAccountRequest.ProtoReflect.Descriptor instead.
func (*UnpauseAccountRequest) Descriptor() ([]byte, []int) {
return file_ra_proto_rawDescGZIP(), []int{14}
return file_ra_proto_rawDescGZIP(), []int{13}
}
func (x *UnpauseAccountRequest) GetRegistrationID() int64 {
@ -855,7 +803,7 @@ type UnpauseAccountResponse struct {
func (x *UnpauseAccountResponse) Reset() {
*x = UnpauseAccountResponse{}
mi := &file_ra_proto_msgTypes[15]
mi := &file_ra_proto_msgTypes[14]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -867,7 +815,7 @@ func (x *UnpauseAccountResponse) String() string {
func (*UnpauseAccountResponse) ProtoMessage() {}
func (x *UnpauseAccountResponse) ProtoReflect() protoreflect.Message {
mi := &file_ra_proto_msgTypes[15]
mi := &file_ra_proto_msgTypes[14]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -880,7 +828,7 @@ func (x *UnpauseAccountResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use UnpauseAccountResponse.ProtoReflect.Descriptor instead.
func (*UnpauseAccountResponse) Descriptor() ([]byte, []int) {
return file_ra_proto_rawDescGZIP(), []int{15}
return file_ra_proto_rawDescGZIP(), []int{14}
}
func (x *UnpauseAccountResponse) GetCount() int64 {
@ -904,7 +852,7 @@ type AddRateLimitOverrideRequest struct {
func (x *AddRateLimitOverrideRequest) Reset() {
*x = AddRateLimitOverrideRequest{}
mi := &file_ra_proto_msgTypes[16]
mi := &file_ra_proto_msgTypes[15]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -916,7 +864,7 @@ func (x *AddRateLimitOverrideRequest) String() string {
func (*AddRateLimitOverrideRequest) ProtoMessage() {}
func (x *AddRateLimitOverrideRequest) ProtoReflect() protoreflect.Message {
mi := &file_ra_proto_msgTypes[16]
mi := &file_ra_proto_msgTypes[15]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -929,7 +877,7 @@ func (x *AddRateLimitOverrideRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use AddRateLimitOverrideRequest.ProtoReflect.Descriptor instead.
func (*AddRateLimitOverrideRequest) Descriptor() ([]byte, []int) {
return file_ra_proto_rawDescGZIP(), []int{16}
return file_ra_proto_rawDescGZIP(), []int{15}
}
func (x *AddRateLimitOverrideRequest) GetLimitEnum() int64 {
@ -984,7 +932,7 @@ type AddRateLimitOverrideResponse struct {
func (x *AddRateLimitOverrideResponse) Reset() {
*x = AddRateLimitOverrideResponse{}
mi := &file_ra_proto_msgTypes[17]
mi := &file_ra_proto_msgTypes[16]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -996,7 +944,7 @@ func (x *AddRateLimitOverrideResponse) String() string {
func (*AddRateLimitOverrideResponse) ProtoMessage() {}
func (x *AddRateLimitOverrideResponse) ProtoReflect() protoreflect.Message {
mi := &file_ra_proto_msgTypes[17]
mi := &file_ra_proto_msgTypes[16]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -1009,7 +957,7 @@ func (x *AddRateLimitOverrideResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use AddRateLimitOverrideResponse.ProtoReflect.Descriptor instead.
func (*AddRateLimitOverrideResponse) Descriptor() ([]byte, []int) {
return file_ra_proto_rawDescGZIP(), []int{17}
return file_ra_proto_rawDescGZIP(), []int{16}
}
func (x *AddRateLimitOverrideResponse) GetInserted() bool {
@ -1044,193 +992,181 @@ var file_ra_proto_rawDesc = string([]byte{
0x28, 0x0c, 0x52, 0x06, 0x73, 0x63, 0x74, 0x44, 0x45, 0x52, 0x22, 0x2d, 0x0a, 0x13, 0x47, 0x65,
0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x4f, 0x43, 0x53, 0x50, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x06, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x22, 0x66, 0x0a, 0x20, 0x55, 0x70, 0x64,
0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43,
0x6f, 0x6e, 0x74, 0x61, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a,
0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18,
0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74,
0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x63, 0x74,
0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x63, 0x74,
0x73, 0x22, 0x58, 0x0a, 0x1c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73,
0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x12, 0x26, 0x0a, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f,
0x6e, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73,
0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x10, 0x0a, 0x03, 0x6a, 0x77, 0x6b,
0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6a, 0x77, 0x6b, 0x22, 0x47, 0x0a, 0x1d, 0x44,
0x65, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0e,
0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x01,
0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69,
0x6f, 0x6e, 0x49, 0x44, 0x22, 0x9c, 0x01, 0x0a, 0x1a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41,
0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x05, 0x61, 0x75, 0x74, 0x68, 0x7a, 0x18, 0x01, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x13, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72,
0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x05, 0x61, 0x75, 0x74, 0x68, 0x7a, 0x12, 0x26,
0x0a, 0x0e, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78,
0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67,
0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x2b, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e,
0x43, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x52, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x22, 0x6d, 0x0a, 0x18, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x56, 0x61,
0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
0x29, 0x0a, 0x05, 0x61, 0x75, 0x74, 0x68, 0x7a, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13,
0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74,
0x69, 0x6f, 0x6e, 0x52, 0x05, 0x61, 0x75, 0x74, 0x68, 0x7a, 0x12, 0x26, 0x0a, 0x0e, 0x63, 0x68,
0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01,
0x28, 0x03, 0x52, 0x0e, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x49, 0x6e, 0x64,
0x65, 0x78, 0x22, 0x5c, 0x0a, 0x1c, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74,
0x42, 0x79, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65,
0x09, 0x52, 0x06, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x22, 0x58, 0x0a, 0x1c, 0x55, 0x70, 0x64,
0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4b,
0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0e, 0x72, 0x65, 0x67,
0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28,
0x03, 0x52, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49,
0x44, 0x12, 0x10, 0x0a, 0x03, 0x6a, 0x77, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03,
0x6a, 0x77, 0x6b, 0x22, 0x47, 0x0a, 0x1d, 0x44, 0x65, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74,
0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x65,
0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x22, 0x9c, 0x01, 0x0a,
0x1a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x05, 0x61,
0x75, 0x74, 0x68, 0x7a, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x63, 0x6f, 0x72,
0x65, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52,
0x05, 0x61, 0x75, 0x74, 0x68, 0x7a, 0x12, 0x26, 0x0a, 0x0e, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65,
0x6e, 0x67, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e,
0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x2b,
0x0a, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x0f, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x43, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67,
0x65, 0x52, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x6d, 0x0a, 0x18, 0x50,
0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x05, 0x61, 0x75, 0x74, 0x68, 0x7a,
0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x75,
0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x05, 0x61, 0x75, 0x74,
0x68, 0x7a, 0x12, 0x26, 0x0a, 0x0e, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x49,
0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x63, 0x68, 0x61, 0x6c,
0x6c, 0x65, 0x6e, 0x67, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x5c, 0x0a, 0x1c, 0x52, 0x65,
0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x42, 0x79, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63,
0x61, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x65,
0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x63, 0x65, 0x72, 0x74, 0x12, 0x12,
0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x63, 0x6f,
0x64, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x65, 0x67, 0x49, 0x44, 0x18, 0x03, 0x20, 0x01, 0x28,
0x03, 0x52, 0x05, 0x72, 0x65, 0x67, 0x49, 0x44, 0x22, 0x32, 0x0a, 0x16, 0x52, 0x65, 0x76, 0x6f,
0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x42, 0x79, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x65, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c,
0x52, 0x04, 0x63, 0x65, 0x72, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02,
0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x65,
0x67, 0x49, 0x44, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x72, 0x65, 0x67, 0x49, 0x44,
0x22, 0x32, 0x0a, 0x16, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x42, 0x79,
0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x65,
0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x63, 0x65, 0x72, 0x74, 0x4a, 0x04,
0x08, 0x02, 0x10, 0x03, 0x22, 0xe6, 0x01, 0x0a, 0x28, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x69, 0x73,
0x74, 0x72, 0x61, 0x74, 0x69, 0x76, 0x65, 0x6c, 0x79, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43,
0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x65, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52,
0x04, 0x63, 0x65, 0x72, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x18,
0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x12, 0x12, 0x0a,
0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x63, 0x6f, 0x64,
0x65, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x03,
0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12,
0x22, 0x0a, 0x0c, 0x73, 0x6b, 0x69, 0x70, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4b, 0x65, 0x79, 0x18,
0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x73, 0x6b, 0x69, 0x70, 0x42, 0x6c, 0x6f, 0x63, 0x6b,
0x4b, 0x65, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x6d, 0x61, 0x6c, 0x66, 0x6f, 0x72, 0x6d, 0x65, 0x64,
0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x6d, 0x61, 0x6c, 0x66, 0x6f, 0x72, 0x6d, 0x65,
0x64, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x72, 0x6c, 0x53, 0x68, 0x61, 0x72, 0x64, 0x18, 0x07, 0x20,
0x01, 0x28, 0x03, 0x52, 0x08, 0x63, 0x72, 0x6c, 0x53, 0x68, 0x61, 0x72, 0x64, 0x22, 0xfb, 0x01,
0x0a, 0x0f, 0x4e, 0x65, 0x77, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x12, 0x26, 0x0a, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f,
0x6e, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73,
0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x32, 0x0a, 0x0b, 0x69, 0x64, 0x65,
0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10,
0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72,
0x52, 0x0b, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x12, 0x36, 0x0a,
0x16, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x66,
0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x16, 0x63,
0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c,
0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65,
0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65,
0x73, 0x12, 0x26, 0x0a, 0x0e, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x73, 0x53, 0x65, 0x72,
0x69, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x72, 0x65, 0x70, 0x6c, 0x61,
0x63, 0x65, 0x73, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x4a,
0x04, 0x08, 0x04, 0x10, 0x05, 0x4a, 0x04, 0x08, 0x06, 0x10, 0x07, 0x22, 0x29, 0x0a, 0x17, 0x47,
0x65, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x22, 0x4b, 0x0a, 0x14, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69,
0x7a, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21,
0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e,
0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65,
0x72, 0x12, 0x10, 0x0a, 0x03, 0x63, 0x73, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03,
0x63, 0x73, 0x72, 0x22, 0x3f, 0x0a, 0x15, 0x55, 0x6e, 0x70, 0x61, 0x75, 0x73, 0x65, 0x41, 0x63,
0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0e,
0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x01,
0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69,
0x6f, 0x6e, 0x49, 0x44, 0x22, 0x2e, 0x0a, 0x16, 0x55, 0x6e, 0x70, 0x61, 0x75, 0x73, 0x65, 0x41,
0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14,
0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63,
0x6f, 0x75, 0x6e, 0x74, 0x22, 0xd2, 0x01, 0x0a, 0x1b, 0x41, 0x64, 0x64, 0x52, 0x61, 0x74, 0x65,
0x4c, 0x69, 0x6d, 0x69, 0x74, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x45, 0x6e, 0x75,
0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x45, 0x6e,
0x75, 0x6d, 0x12, 0x1c, 0x0a, 0x09, 0x62, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x18,
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x62, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x4b, 0x65, 0x79,
0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28,
0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x31, 0x0a, 0x06, 0x70, 0x65,
0x72, 0x69, 0x6f, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f,
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x12, 0x14, 0x0a,
0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f,
0x75, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x75, 0x72, 0x73, 0x74, 0x18, 0x06, 0x20, 0x01,
0x28, 0x03, 0x52, 0x05, 0x62, 0x75, 0x72, 0x73, 0x74, 0x22, 0x54, 0x0a, 0x1c, 0x41, 0x64, 0x64,
0x52, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64,
0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x73,
0x65, 0x72, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x6e, 0x73,
0x65, 0x72, 0x74, 0x65, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64,
0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x32,
0x87, 0x09, 0x0a, 0x15, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x3b, 0x0a, 0x0f, 0x4e, 0x65, 0x77,
0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x2e, 0x63,
0x6f, 0x72, 0x65, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
0x1a, 0x12, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x12, 0x57, 0x0a, 0x19, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65,
0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74,
0x61, 0x63, 0x74, 0x12, 0x24, 0x2e, 0x72, 0x61, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52,
0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x61,
0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x63, 0x6f, 0x72, 0x65,
0x52, 0x04, 0x63, 0x65, 0x72, 0x74, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x22, 0xe6, 0x01, 0x0a,
0x28, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x76, 0x65, 0x6c,
0x79, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61,
0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x65, 0x72,
0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x63, 0x65, 0x72, 0x74, 0x12, 0x16, 0x0a,
0x06, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73,
0x65, 0x72, 0x69, 0x61, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20,
0x01, 0x28, 0x03, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x64, 0x6d,
0x69, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x64,
0x6d, 0x69, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x73, 0x6b, 0x69, 0x70, 0x42,
0x6c, 0x6f, 0x63, 0x6b, 0x4b, 0x65, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x73,
0x6b, 0x69, 0x70, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4b, 0x65, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x6d,
0x61, 0x6c, 0x66, 0x6f, 0x72, 0x6d, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09,
0x6d, 0x61, 0x6c, 0x66, 0x6f, 0x72, 0x6d, 0x65, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x72, 0x6c,
0x53, 0x68, 0x61, 0x72, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x63, 0x72, 0x6c,
0x53, 0x68, 0x61, 0x72, 0x64, 0x22, 0xfb, 0x01, 0x0a, 0x0f, 0x4e, 0x65, 0x77, 0x4f, 0x72, 0x64,
0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0e, 0x72, 0x65, 0x67,
0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28,
0x03, 0x52, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49,
0x44, 0x12, 0x32, 0x0a, 0x0b, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73,
0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x49, 0x64,
0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x52, 0x0b, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69,
0x66, 0x69, 0x65, 0x72, 0x73, 0x12, 0x36, 0x0a, 0x16, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69,
0x63, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x18,
0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x16, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61,
0x74, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a,
0x08, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52,
0x08, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x73, 0x12, 0x26, 0x0a, 0x0e, 0x72, 0x65, 0x70,
0x6c, 0x61, 0x63, 0x65, 0x73, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28,
0x09, 0x52, 0x0e, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x73, 0x53, 0x65, 0x72, 0x69, 0x61,
0x6c, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x4a, 0x04, 0x08,
0x06, 0x10, 0x07, 0x22, 0x29, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72,
0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e,
0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x22, 0x4b,
0x0a, 0x14, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18,
0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4f, 0x72, 0x64,
0x65, 0x72, 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x10, 0x0a, 0x03, 0x63, 0x73, 0x72,
0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x63, 0x73, 0x72, 0x22, 0x3f, 0x0a, 0x15, 0x55,
0x6e, 0x70, 0x61, 0x75, 0x73, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x65,
0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x22, 0x2e, 0x0a, 0x16,
0x55, 0x6e, 0x70, 0x61, 0x75, 0x73, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18,
0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xd2, 0x01, 0x0a,
0x1b, 0x41, 0x64, 0x64, 0x52, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x4f, 0x76, 0x65,
0x72, 0x72, 0x69, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09,
0x6c, 0x69, 0x6d, 0x69, 0x74, 0x45, 0x6e, 0x75, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52,
0x09, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x45, 0x6e, 0x75, 0x6d, 0x12, 0x1c, 0x0a, 0x09, 0x62, 0x75,
0x63, 0x6b, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x62,
0x75, 0x63, 0x6b, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d,
0x65, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x65,
0x6e, 0x74, 0x12, 0x31, 0x0a, 0x06, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x18, 0x04, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x70,
0x65, 0x72, 0x69, 0x6f, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05,
0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x62,
0x75, 0x72, 0x73, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x62, 0x75, 0x72, 0x73,
0x74, 0x22, 0x54, 0x0a, 0x1c, 0x41, 0x64, 0x64, 0x52, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69,
0x74, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20,
0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x65, 0x64, 0x12, 0x18, 0x0a,
0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07,
0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x32, 0xae, 0x08, 0x0a, 0x15, 0x52, 0x65, 0x67, 0x69,
0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74,
0x79, 0x12, 0x3b, 0x0a, 0x0f, 0x4e, 0x65, 0x77, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x52, 0x65, 0x67, 0x69,
0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x12, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e,
0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x12, 0x4f,
0x0a, 0x15, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79, 0x12, 0x20, 0x2e, 0x72, 0x61, 0x2e, 0x55, 0x70, 0x64,
0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4b,
0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x63, 0x6f, 0x72, 0x65,
0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x12,
0x4f, 0x0a, 0x15, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79, 0x12, 0x20, 0x2e, 0x72, 0x61, 0x2e, 0x55, 0x70,
0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x63, 0x6f, 0x72,
0x65, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00,
0x12, 0x51, 0x0a, 0x16, 0x44, 0x65, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x52, 0x65,
0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x2e, 0x72, 0x61, 0x2e,
0x44, 0x65, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74,
0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e,
0x63, 0x6f, 0x72, 0x65, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f,
0x6e, 0x22, 0x00, 0x12, 0x48, 0x0a, 0x11, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x56, 0x61,
0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x2e, 0x72, 0x61, 0x2e, 0x50, 0x65,
0x72, 0x66, 0x6f, 0x72, 0x6d, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x75,
0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x12, 0x48, 0x0a,
0x17, 0x44, 0x65, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f,
0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x13, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e,
0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x16, 0x2e,
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x53, 0x0a, 0x15, 0x52, 0x65, 0x76, 0x6f, 0x6b,
0x65, 0x43, 0x65, 0x72, 0x74, 0x42, 0x79, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x6e, 0x74,
0x12, 0x20, 0x2e, 0x72, 0x61, 0x2e, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74,
0x42, 0x79, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x0f,
0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x42, 0x79, 0x4b, 0x65, 0x79, 0x12,
0x1a, 0x2e, 0x72, 0x61, 0x2e, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x42,
0x79, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f,
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d,
0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x6b, 0x0a, 0x21, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x69, 0x73,
0x74, 0x72, 0x61, 0x74, 0x69, 0x76, 0x65, 0x6c, 0x79, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43,
0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x2c, 0x2e, 0x72, 0x61, 0x2e,
0x41, 0x64, 0x6d, 0x69, 0x6e, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x76, 0x65, 0x6c, 0x79,
0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74,
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,
0x22, 0x00, 0x12, 0x2e, 0x0a, 0x08, 0x4e, 0x65, 0x77, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x13,
0x2e, 0x72, 0x61, 0x2e, 0x4e, 0x65, 0x77, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x1a, 0x0b, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72,
0x22, 0x00, 0x12, 0x46, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69,
0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x2e, 0x72, 0x61, 0x2e, 0x47, 0x65, 0x74, 0x41,
0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f,
0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x12, 0x38, 0x0a, 0x0d, 0x46, 0x69,
0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x18, 0x2e, 0x72, 0x61,
0x2e, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0b, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4f, 0x72, 0x64,
0x65, 0x72, 0x22, 0x00, 0x12, 0x3b, 0x0a, 0x0c, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65,
0x4f, 0x43, 0x53, 0x50, 0x12, 0x17, 0x2e, 0x72, 0x61, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61,
0x74, 0x65, 0x4f, 0x43, 0x53, 0x50, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x10, 0x2e,
0x63, 0x61, 0x2e, 0x4f, 0x43, 0x53, 0x50, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
0x00, 0x12, 0x49, 0x0a, 0x0e, 0x55, 0x6e, 0x70, 0x61, 0x75, 0x73, 0x65, 0x41, 0x63, 0x63, 0x6f,
0x75, 0x6e, 0x74, 0x12, 0x19, 0x2e, 0x72, 0x61, 0x2e, 0x55, 0x6e, 0x70, 0x61, 0x75, 0x73, 0x65,
0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a,
0x2e, 0x72, 0x61, 0x2e, 0x55, 0x6e, 0x70, 0x61, 0x75, 0x73, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75,
0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5b, 0x0a, 0x14,
0x41, 0x64, 0x64, 0x52, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x4f, 0x76, 0x65, 0x72,
0x72, 0x69, 0x64, 0x65, 0x12, 0x1f, 0x2e, 0x72, 0x61, 0x2e, 0x41, 0x64, 0x64, 0x52, 0x61, 0x74,
0x51, 0x0a, 0x16, 0x44, 0x65, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67,
0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x2e, 0x72, 0x61, 0x2e, 0x44,
0x65, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x63,
0x6f, 0x72, 0x65, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
0x22, 0x00, 0x12, 0x48, 0x0a, 0x11, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x56, 0x61, 0x6c,
0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x2e, 0x72, 0x61, 0x2e, 0x50, 0x65, 0x72,
0x66, 0x6f, 0x72, 0x6d, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x75, 0x74,
0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x12, 0x48, 0x0a, 0x17,
0x44, 0x65, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72,
0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x13, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x41,
0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x16, 0x2e, 0x67,
0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45,
0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x53, 0x0a, 0x15, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65,
0x43, 0x65, 0x72, 0x74, 0x42, 0x79, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x6e, 0x74, 0x12,
0x20, 0x2e, 0x72, 0x61, 0x2e, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x42,
0x79, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x0f, 0x52,
0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x42, 0x79, 0x4b, 0x65, 0x79, 0x12, 0x1a,
0x2e, 0x72, 0x61, 0x2e, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x42, 0x79,
0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f,
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70,
0x74, 0x79, 0x22, 0x00, 0x12, 0x6b, 0x0a, 0x21, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x69, 0x73, 0x74,
0x72, 0x61, 0x74, 0x69, 0x76, 0x65, 0x6c, 0x79, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65,
0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x2c, 0x2e, 0x72, 0x61, 0x2e, 0x41,
0x64, 0x6d, 0x69, 0x6e, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x76, 0x65, 0x6c, 0x79, 0x52,
0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22,
0x00, 0x12, 0x2e, 0x0a, 0x08, 0x4e, 0x65, 0x77, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x13, 0x2e,
0x72, 0x61, 0x2e, 0x4e, 0x65, 0x77, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x0b, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x22,
0x00, 0x12, 0x46, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x2e, 0x72, 0x61, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x75,
0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x13, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72,
0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x12, 0x38, 0x0a, 0x0d, 0x46, 0x69, 0x6e,
0x61, 0x6c, 0x69, 0x7a, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x18, 0x2e, 0x72, 0x61, 0x2e,
0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x0b, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4f, 0x72, 0x64, 0x65,
0x72, 0x22, 0x00, 0x12, 0x3b, 0x0a, 0x0c, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x4f,
0x43, 0x53, 0x50, 0x12, 0x17, 0x2e, 0x72, 0x61, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74,
0x65, 0x4f, 0x43, 0x53, 0x50, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x10, 0x2e, 0x63,
0x61, 0x2e, 0x4f, 0x43, 0x53, 0x50, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,
0x12, 0x49, 0x0a, 0x0e, 0x55, 0x6e, 0x70, 0x61, 0x75, 0x73, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75,
0x6e, 0x74, 0x12, 0x19, 0x2e, 0x72, 0x61, 0x2e, 0x55, 0x6e, 0x70, 0x61, 0x75, 0x73, 0x65, 0x41,
0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e,
0x72, 0x61, 0x2e, 0x55, 0x6e, 0x70, 0x61, 0x75, 0x73, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e,
0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5b, 0x0a, 0x14, 0x41,
0x64, 0x64, 0x52, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x4f, 0x76, 0x65, 0x72, 0x72,
0x69, 0x64, 0x65, 0x12, 0x1f, 0x2e, 0x72, 0x61, 0x2e, 0x41, 0x64, 0x64, 0x52, 0x61, 0x74, 0x65,
0x4c, 0x69, 0x6d, 0x69, 0x74, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x72, 0x61, 0x2e, 0x41, 0x64, 0x64, 0x52, 0x61, 0x74,
0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x72, 0x61, 0x2e, 0x41, 0x64, 0x64, 0x52, 0x61,
0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0x3b, 0x0a, 0x0b, 0x53, 0x43, 0x54,
0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x2c, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x53,
0x43, 0x54, 0x73, 0x12, 0x0e, 0x2e, 0x72, 0x61, 0x2e, 0x53, 0x43, 0x54, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x72, 0x61, 0x2e, 0x53, 0x43, 0x54, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x65, 0x74, 0x73, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74,
0x2f, 0x62, 0x6f, 0x75, 0x6c, 0x64, 0x65, 0x72, 0x2f, 0x72, 0x61, 0x2f, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0x3b, 0x0a, 0x0b, 0x53, 0x43, 0x54, 0x50,
0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x2c, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x53, 0x43,
0x54, 0x73, 0x12, 0x0e, 0x2e, 0x72, 0x61, 0x2e, 0x53, 0x43, 0x54, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x72, 0x61, 0x2e, 0x53, 0x43, 0x54, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e,
0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x65, 0x74, 0x73, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x2f,
0x62, 0x6f, 0x75, 0x6c, 0x64, 0x65, 0x72, 0x2f, 0x72, 0x61, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
})
var (
@ -1245,76 +1181,73 @@ func file_ra_proto_rawDescGZIP() []byte {
return file_ra_proto_rawDescData
}
var file_ra_proto_msgTypes = make([]protoimpl.MessageInfo, 18)
var file_ra_proto_msgTypes = make([]protoimpl.MessageInfo, 17)
var file_ra_proto_goTypes = []any{
(*SCTRequest)(nil), // 0: ra.SCTRequest
(*SCTResponse)(nil), // 1: ra.SCTResponse
(*GenerateOCSPRequest)(nil), // 2: ra.GenerateOCSPRequest
(*UpdateRegistrationContactRequest)(nil), // 3: ra.UpdateRegistrationContactRequest
(*UpdateRegistrationKeyRequest)(nil), // 4: ra.UpdateRegistrationKeyRequest
(*DeactivateRegistrationRequest)(nil), // 5: ra.DeactivateRegistrationRequest
(*UpdateAuthorizationRequest)(nil), // 6: ra.UpdateAuthorizationRequest
(*PerformValidationRequest)(nil), // 7: ra.PerformValidationRequest
(*RevokeCertByApplicantRequest)(nil), // 8: ra.RevokeCertByApplicantRequest
(*RevokeCertByKeyRequest)(nil), // 9: ra.RevokeCertByKeyRequest
(*AdministrativelyRevokeCertificateRequest)(nil), // 10: ra.AdministrativelyRevokeCertificateRequest
(*NewOrderRequest)(nil), // 11: ra.NewOrderRequest
(*GetAuthorizationRequest)(nil), // 12: ra.GetAuthorizationRequest
(*FinalizeOrderRequest)(nil), // 13: ra.FinalizeOrderRequest
(*UnpauseAccountRequest)(nil), // 14: ra.UnpauseAccountRequest
(*UnpauseAccountResponse)(nil), // 15: ra.UnpauseAccountResponse
(*AddRateLimitOverrideRequest)(nil), // 16: ra.AddRateLimitOverrideRequest
(*AddRateLimitOverrideResponse)(nil), // 17: ra.AddRateLimitOverrideResponse
(*proto.Authorization)(nil), // 18: core.Authorization
(*proto.Challenge)(nil), // 19: core.Challenge
(*proto.Identifier)(nil), // 20: core.Identifier
(*proto.Order)(nil), // 21: core.Order
(*durationpb.Duration)(nil), // 22: google.protobuf.Duration
(*proto.Registration)(nil), // 23: core.Registration
(*emptypb.Empty)(nil), // 24: google.protobuf.Empty
(*proto1.OCSPResponse)(nil), // 25: ca.OCSPResponse
(*UpdateRegistrationKeyRequest)(nil), // 3: ra.UpdateRegistrationKeyRequest
(*DeactivateRegistrationRequest)(nil), // 4: ra.DeactivateRegistrationRequest
(*UpdateAuthorizationRequest)(nil), // 5: ra.UpdateAuthorizationRequest
(*PerformValidationRequest)(nil), // 6: ra.PerformValidationRequest
(*RevokeCertByApplicantRequest)(nil), // 7: ra.RevokeCertByApplicantRequest
(*RevokeCertByKeyRequest)(nil), // 8: ra.RevokeCertByKeyRequest
(*AdministrativelyRevokeCertificateRequest)(nil), // 9: ra.AdministrativelyRevokeCertificateRequest
(*NewOrderRequest)(nil), // 10: ra.NewOrderRequest
(*GetAuthorizationRequest)(nil), // 11: ra.GetAuthorizationRequest
(*FinalizeOrderRequest)(nil), // 12: ra.FinalizeOrderRequest
(*UnpauseAccountRequest)(nil), // 13: ra.UnpauseAccountRequest
(*UnpauseAccountResponse)(nil), // 14: ra.UnpauseAccountResponse
(*AddRateLimitOverrideRequest)(nil), // 15: ra.AddRateLimitOverrideRequest
(*AddRateLimitOverrideResponse)(nil), // 16: ra.AddRateLimitOverrideResponse
(*proto.Authorization)(nil), // 17: core.Authorization
(*proto.Challenge)(nil), // 18: core.Challenge
(*proto.Identifier)(nil), // 19: core.Identifier
(*proto.Order)(nil), // 20: core.Order
(*durationpb.Duration)(nil), // 21: google.protobuf.Duration
(*proto.Registration)(nil), // 22: core.Registration
(*emptypb.Empty)(nil), // 23: google.protobuf.Empty
(*proto1.OCSPResponse)(nil), // 24: ca.OCSPResponse
}
var file_ra_proto_depIdxs = []int32{
18, // 0: ra.UpdateAuthorizationRequest.authz:type_name -> core.Authorization
19, // 1: ra.UpdateAuthorizationRequest.response:type_name -> core.Challenge
18, // 2: ra.PerformValidationRequest.authz:type_name -> core.Authorization
20, // 3: ra.NewOrderRequest.identifiers:type_name -> core.Identifier
21, // 4: ra.FinalizeOrderRequest.order:type_name -> core.Order
22, // 5: ra.AddRateLimitOverrideRequest.period:type_name -> google.protobuf.Duration
23, // 6: ra.RegistrationAuthority.NewRegistration:input_type -> core.Registration
3, // 7: ra.RegistrationAuthority.UpdateRegistrationContact:input_type -> ra.UpdateRegistrationContactRequest
4, // 8: ra.RegistrationAuthority.UpdateRegistrationKey:input_type -> ra.UpdateRegistrationKeyRequest
5, // 9: ra.RegistrationAuthority.DeactivateRegistration:input_type -> ra.DeactivateRegistrationRequest
7, // 10: ra.RegistrationAuthority.PerformValidation:input_type -> ra.PerformValidationRequest
18, // 11: ra.RegistrationAuthority.DeactivateAuthorization:input_type -> core.Authorization
8, // 12: ra.RegistrationAuthority.RevokeCertByApplicant:input_type -> ra.RevokeCertByApplicantRequest
9, // 13: ra.RegistrationAuthority.RevokeCertByKey:input_type -> ra.RevokeCertByKeyRequest
10, // 14: ra.RegistrationAuthority.AdministrativelyRevokeCertificate:input_type -> ra.AdministrativelyRevokeCertificateRequest
11, // 15: ra.RegistrationAuthority.NewOrder:input_type -> ra.NewOrderRequest
12, // 16: ra.RegistrationAuthority.GetAuthorization:input_type -> ra.GetAuthorizationRequest
13, // 17: ra.RegistrationAuthority.FinalizeOrder:input_type -> ra.FinalizeOrderRequest
2, // 18: ra.RegistrationAuthority.GenerateOCSP:input_type -> ra.GenerateOCSPRequest
14, // 19: ra.RegistrationAuthority.UnpauseAccount:input_type -> ra.UnpauseAccountRequest
16, // 20: ra.RegistrationAuthority.AddRateLimitOverride:input_type -> ra.AddRateLimitOverrideRequest
0, // 21: ra.SCTProvider.GetSCTs:input_type -> ra.SCTRequest
23, // 22: ra.RegistrationAuthority.NewRegistration:output_type -> core.Registration
23, // 23: ra.RegistrationAuthority.UpdateRegistrationContact:output_type -> core.Registration
23, // 24: ra.RegistrationAuthority.UpdateRegistrationKey:output_type -> core.Registration
23, // 25: ra.RegistrationAuthority.DeactivateRegistration:output_type -> core.Registration
18, // 26: ra.RegistrationAuthority.PerformValidation:output_type -> core.Authorization
24, // 27: ra.RegistrationAuthority.DeactivateAuthorization:output_type -> google.protobuf.Empty
24, // 28: ra.RegistrationAuthority.RevokeCertByApplicant:output_type -> google.protobuf.Empty
24, // 29: ra.RegistrationAuthority.RevokeCertByKey:output_type -> google.protobuf.Empty
24, // 30: ra.RegistrationAuthority.AdministrativelyRevokeCertificate:output_type -> google.protobuf.Empty
21, // 31: ra.RegistrationAuthority.NewOrder:output_type -> core.Order
18, // 32: ra.RegistrationAuthority.GetAuthorization:output_type -> core.Authorization
21, // 33: ra.RegistrationAuthority.FinalizeOrder:output_type -> core.Order
25, // 34: ra.RegistrationAuthority.GenerateOCSP:output_type -> ca.OCSPResponse
15, // 35: ra.RegistrationAuthority.UnpauseAccount:output_type -> ra.UnpauseAccountResponse
17, // 36: ra.RegistrationAuthority.AddRateLimitOverride:output_type -> ra.AddRateLimitOverrideResponse
1, // 37: ra.SCTProvider.GetSCTs:output_type -> ra.SCTResponse
22, // [22:38] is the sub-list for method output_type
6, // [6:22] is the sub-list for method input_type
17, // 0: ra.UpdateAuthorizationRequest.authz:type_name -> core.Authorization
18, // 1: ra.UpdateAuthorizationRequest.response:type_name -> core.Challenge
17, // 2: ra.PerformValidationRequest.authz:type_name -> core.Authorization
19, // 3: ra.NewOrderRequest.identifiers:type_name -> core.Identifier
20, // 4: ra.FinalizeOrderRequest.order:type_name -> core.Order
21, // 5: ra.AddRateLimitOverrideRequest.period:type_name -> google.protobuf.Duration
22, // 6: ra.RegistrationAuthority.NewRegistration:input_type -> core.Registration
3, // 7: ra.RegistrationAuthority.UpdateRegistrationKey:input_type -> ra.UpdateRegistrationKeyRequest
4, // 8: ra.RegistrationAuthority.DeactivateRegistration:input_type -> ra.DeactivateRegistrationRequest
6, // 9: ra.RegistrationAuthority.PerformValidation:input_type -> ra.PerformValidationRequest
17, // 10: ra.RegistrationAuthority.DeactivateAuthorization:input_type -> core.Authorization
7, // 11: ra.RegistrationAuthority.RevokeCertByApplicant:input_type -> ra.RevokeCertByApplicantRequest
8, // 12: ra.RegistrationAuthority.RevokeCertByKey:input_type -> ra.RevokeCertByKeyRequest
9, // 13: ra.RegistrationAuthority.AdministrativelyRevokeCertificate:input_type -> ra.AdministrativelyRevokeCertificateRequest
10, // 14: ra.RegistrationAuthority.NewOrder:input_type -> ra.NewOrderRequest
11, // 15: ra.RegistrationAuthority.GetAuthorization:input_type -> ra.GetAuthorizationRequest
12, // 16: ra.RegistrationAuthority.FinalizeOrder:input_type -> ra.FinalizeOrderRequest
2, // 17: ra.RegistrationAuthority.GenerateOCSP:input_type -> ra.GenerateOCSPRequest
13, // 18: ra.RegistrationAuthority.UnpauseAccount:input_type -> ra.UnpauseAccountRequest
15, // 19: ra.RegistrationAuthority.AddRateLimitOverride:input_type -> ra.AddRateLimitOverrideRequest
0, // 20: ra.SCTProvider.GetSCTs:input_type -> ra.SCTRequest
22, // 21: ra.RegistrationAuthority.NewRegistration:output_type -> core.Registration
22, // 22: ra.RegistrationAuthority.UpdateRegistrationKey:output_type -> core.Registration
22, // 23: ra.RegistrationAuthority.DeactivateRegistration:output_type -> core.Registration
17, // 24: ra.RegistrationAuthority.PerformValidation:output_type -> core.Authorization
23, // 25: ra.RegistrationAuthority.DeactivateAuthorization:output_type -> google.protobuf.Empty
23, // 26: ra.RegistrationAuthority.RevokeCertByApplicant:output_type -> google.protobuf.Empty
23, // 27: ra.RegistrationAuthority.RevokeCertByKey:output_type -> google.protobuf.Empty
23, // 28: ra.RegistrationAuthority.AdministrativelyRevokeCertificate:output_type -> google.protobuf.Empty
20, // 29: ra.RegistrationAuthority.NewOrder:output_type -> core.Order
17, // 30: ra.RegistrationAuthority.GetAuthorization:output_type -> core.Authorization
20, // 31: ra.RegistrationAuthority.FinalizeOrder:output_type -> core.Order
24, // 32: ra.RegistrationAuthority.GenerateOCSP:output_type -> ca.OCSPResponse
14, // 33: ra.RegistrationAuthority.UnpauseAccount:output_type -> ra.UnpauseAccountResponse
16, // 34: ra.RegistrationAuthority.AddRateLimitOverride:output_type -> ra.AddRateLimitOverrideResponse
1, // 35: ra.SCTProvider.GetSCTs:output_type -> ra.SCTResponse
21, // [21:36] is the sub-list for method output_type
6, // [6:21] is the sub-list for method input_type
6, // [6:6] is the sub-list for extension type_name
6, // [6:6] is the sub-list for extension extendee
0, // [0:6] is the sub-list for field type_name
@ -1331,7 +1264,7 @@ func file_ra_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_ra_proto_rawDesc), len(file_ra_proto_rawDesc)),
NumEnums: 0,
NumMessages: 18,
NumMessages: 17,
NumExtensions: 0,
NumServices: 2,
},

View File

@ -10,7 +10,6 @@ import "google/protobuf/duration.proto";
service RegistrationAuthority {
rpc NewRegistration(core.Registration) returns (core.Registration) {}
rpc UpdateRegistrationContact(UpdateRegistrationContactRequest) returns (core.Registration) {}
rpc UpdateRegistrationKey(UpdateRegistrationKeyRequest) returns (core.Registration) {}
rpc DeactivateRegistration(DeactivateRegistrationRequest) returns (core.Registration) {}
rpc PerformValidation(PerformValidationRequest) returns (core.Authorization) {}
@ -43,11 +42,6 @@ message GenerateOCSPRequest {
string serial = 1;
}
message UpdateRegistrationContactRequest {
int64 registrationID = 1;
repeated string contacts = 2;
}
message UpdateRegistrationKeyRequest {
int64 registrationID = 1;
bytes jwk = 2;

View File

@ -23,7 +23,6 @@ const _ = grpc.SupportPackageIsVersion9
const (
RegistrationAuthority_NewRegistration_FullMethodName = "/ra.RegistrationAuthority/NewRegistration"
RegistrationAuthority_UpdateRegistrationContact_FullMethodName = "/ra.RegistrationAuthority/UpdateRegistrationContact"
RegistrationAuthority_UpdateRegistrationKey_FullMethodName = "/ra.RegistrationAuthority/UpdateRegistrationKey"
RegistrationAuthority_DeactivateRegistration_FullMethodName = "/ra.RegistrationAuthority/DeactivateRegistration"
RegistrationAuthority_PerformValidation_FullMethodName = "/ra.RegistrationAuthority/PerformValidation"
@ -44,7 +43,6 @@ const (
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type RegistrationAuthorityClient interface {
NewRegistration(ctx context.Context, in *proto.Registration, opts ...grpc.CallOption) (*proto.Registration, error)
UpdateRegistrationContact(ctx context.Context, in *UpdateRegistrationContactRequest, opts ...grpc.CallOption) (*proto.Registration, error)
UpdateRegistrationKey(ctx context.Context, in *UpdateRegistrationKeyRequest, opts ...grpc.CallOption) (*proto.Registration, error)
DeactivateRegistration(ctx context.Context, in *DeactivateRegistrationRequest, opts ...grpc.CallOption) (*proto.Registration, error)
PerformValidation(ctx context.Context, in *PerformValidationRequest, opts ...grpc.CallOption) (*proto.Authorization, error)
@ -79,16 +77,6 @@ func (c *registrationAuthorityClient) NewRegistration(ctx context.Context, in *p
return out, nil
}
func (c *registrationAuthorityClient) UpdateRegistrationContact(ctx context.Context, in *UpdateRegistrationContactRequest, opts ...grpc.CallOption) (*proto.Registration, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(proto.Registration)
err := c.cc.Invoke(ctx, RegistrationAuthority_UpdateRegistrationContact_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *registrationAuthorityClient) UpdateRegistrationKey(ctx context.Context, in *UpdateRegistrationKeyRequest, opts ...grpc.CallOption) (*proto.Registration, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(proto.Registration)
@ -224,7 +212,6 @@ func (c *registrationAuthorityClient) AddRateLimitOverride(ctx context.Context,
// for forward compatibility.
type RegistrationAuthorityServer interface {
NewRegistration(context.Context, *proto.Registration) (*proto.Registration, error)
UpdateRegistrationContact(context.Context, *UpdateRegistrationContactRequest) (*proto.Registration, error)
UpdateRegistrationKey(context.Context, *UpdateRegistrationKeyRequest) (*proto.Registration, error)
DeactivateRegistration(context.Context, *DeactivateRegistrationRequest) (*proto.Registration, error)
PerformValidation(context.Context, *PerformValidationRequest) (*proto.Authorization, error)
@ -252,9 +239,6 @@ type UnimplementedRegistrationAuthorityServer struct{}
func (UnimplementedRegistrationAuthorityServer) NewRegistration(context.Context, *proto.Registration) (*proto.Registration, error) {
return nil, status.Errorf(codes.Unimplemented, "method NewRegistration not implemented")
}
func (UnimplementedRegistrationAuthorityServer) UpdateRegistrationContact(context.Context, *UpdateRegistrationContactRequest) (*proto.Registration, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateRegistrationContact not implemented")
}
func (UnimplementedRegistrationAuthorityServer) UpdateRegistrationKey(context.Context, *UpdateRegistrationKeyRequest) (*proto.Registration, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateRegistrationKey not implemented")
}
@ -333,24 +317,6 @@ func _RegistrationAuthority_NewRegistration_Handler(srv interface{}, ctx context
return interceptor(ctx, in, info, handler)
}
func _RegistrationAuthority_UpdateRegistrationContact_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(UpdateRegistrationContactRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(RegistrationAuthorityServer).UpdateRegistrationContact(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: RegistrationAuthority_UpdateRegistrationContact_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(RegistrationAuthorityServer).UpdateRegistrationContact(ctx, req.(*UpdateRegistrationContactRequest))
}
return interceptor(ctx, in, info, handler)
}
func _RegistrationAuthority_UpdateRegistrationKey_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(UpdateRegistrationKeyRequest)
if err := dec(in); err != nil {
@ -596,10 +562,6 @@ var RegistrationAuthority_ServiceDesc = grpc.ServiceDesc{
MethodName: "NewRegistration",
Handler: _RegistrationAuthority_NewRegistration_Handler,
},
{
MethodName: "UpdateRegistrationContact",
Handler: _RegistrationAuthority_UpdateRegistrationContact_Handler,
},
{
MethodName: "UpdateRegistrationKey",
Handler: _RegistrationAuthority_UpdateRegistrationKey_Handler,

106
ra/ra.go
View File

@ -111,9 +111,6 @@ type RegistrationAuthorityImpl struct {
// TODO(#8177): Remove once the rate of requests failing to finalize due to
// requesting Must-Staple has diminished.
mustStapleRequestsCounter *prometheus.CounterVec
// TODO(#7966): Remove once the rate of registrations with contacts has been
// determined.
newOrUpdatedContactCounter *prometheus.CounterVec
}
var _ rapb.RegistrationAuthorityServer = (*RegistrationAuthorityImpl)(nil)
@ -230,45 +227,36 @@ func NewRegistrationAuthorityImpl(
}, []string{"allowlist"})
stats.MustRegister(mustStapleRequestsCounter)
// TODO(#7966): Remove once the rate of registrations with contacts has been
// determined.
newOrUpdatedContactCounter := prometheus.NewCounterVec(prometheus.CounterOpts{
Name: "new_or_updated_contact",
Help: "A counter of new or updated contacts, labeled by new=[bool]",
}, []string{"new"})
stats.MustRegister(newOrUpdatedContactCounter)
issuersByNameID := make(map[issuance.NameID]*issuance.Certificate)
for _, issuer := range issuers {
issuersByNameID[issuer.NameID()] = issuer
}
ra := &RegistrationAuthorityImpl{
clk: clk,
log: logger,
profiles: profiles,
maxContactsPerReg: maxContactsPerReg,
keyPolicy: keyPolicy,
limiter: limiter,
txnBuilder: txnBuilder,
publisher: pubc,
finalizeTimeout: finalizeTimeout,
ctpolicy: ctp,
ctpolicyResults: ctpolicyResults,
purger: purger,
issuersByNameID: issuersByNameID,
namesPerCert: namesPerCert,
newRegCounter: newRegCounter,
recheckCAACounter: recheckCAACounter,
newCertCounter: newCertCounter,
revocationReasonCounter: revocationReasonCounter,
authzAges: authzAges,
orderAges: orderAges,
inflightFinalizes: inflightFinalizes,
certCSRMismatch: certCSRMismatch,
pauseCounter: pauseCounter,
mustStapleRequestsCounter: mustStapleRequestsCounter,
newOrUpdatedContactCounter: newOrUpdatedContactCounter,
clk: clk,
log: logger,
profiles: profiles,
maxContactsPerReg: maxContactsPerReg,
keyPolicy: keyPolicy,
limiter: limiter,
txnBuilder: txnBuilder,
publisher: pubc,
finalizeTimeout: finalizeTimeout,
ctpolicy: ctp,
ctpolicyResults: ctpolicyResults,
purger: purger,
issuersByNameID: issuersByNameID,
namesPerCert: namesPerCert,
newRegCounter: newRegCounter,
recheckCAACounter: recheckCAACounter,
newCertCounter: newCertCounter,
revocationReasonCounter: revocationReasonCounter,
authzAges: authzAges,
orderAges: orderAges,
inflightFinalizes: inflightFinalizes,
certCSRMismatch: certCSRMismatch,
pauseCounter: pauseCounter,
mustStapleRequestsCounter: mustStapleRequestsCounter,
}
return ra
}
@ -527,17 +515,9 @@ func (ra *RegistrationAuthorityImpl) NewRegistration(ctx context.Context, reques
return nil, berrors.MalformedError("invalid public key: %s", err.Error())
}
// Check that contacts conform to our expectations.
// TODO(#8199): Remove this when no contacts are included in any requests.
err = ra.validateContacts(request.Contact)
if err != nil {
return nil, err
}
// Don't populate ID or CreatedAt because those will be set by the SA.
req := &corepb.Registration{
Key: request.Key,
Contact: request.Contact,
Agreement: request.Agreement,
Status: string(core.StatusValid),
}
@ -548,12 +528,6 @@ func (ra *RegistrationAuthorityImpl) NewRegistration(ctx context.Context, reques
return nil, err
}
// TODO(#7966): Remove once the rate of registrations with contacts has been
// determined.
for range request.Contact {
ra.newOrUpdatedContactCounter.With(prometheus.Labels{"new": "true"}).Inc()
}
ra.newRegCounter.Inc()
return res, nil
}
@ -1400,38 +1374,6 @@ func (ra *RegistrationAuthorityImpl) getSCTs(ctx context.Context, precertDER []b
return scts, nil
}
// UpdateRegistrationContact updates an existing Registration's contact. The
// updated contacts field may be empty.
//
// Deprecated: This method has no callers. See
// https://github.com/letsencrypt/boulder/issues/8199 for removal.
func (ra *RegistrationAuthorityImpl) UpdateRegistrationContact(ctx context.Context, req *rapb.UpdateRegistrationContactRequest) (*corepb.Registration, error) {
if core.IsAnyNilOrZero(req.RegistrationID) {
return nil, errIncompleteGRPCRequest
}
err := ra.validateContacts(req.Contacts)
if err != nil {
return nil, fmt.Errorf("invalid contact: %w", err)
}
update, err := ra.SA.UpdateRegistrationContact(ctx, &sapb.UpdateRegistrationContactRequest{
RegistrationID: req.RegistrationID,
Contacts: req.Contacts,
})
if err != nil {
return nil, fmt.Errorf("failed to update registration contact: %w", err)
}
// TODO(#7966): Remove once the rate of registrations with contacts has
// been determined.
for range req.Contacts {
ra.newOrUpdatedContactCounter.With(prometheus.Labels{"new": "false"}).Inc()
}
return update, nil
}
// UpdateRegistrationKey updates an existing Registration's key.
func (ra *RegistrationAuthorityImpl) UpdateRegistrationKey(ctx context.Context, req *rapb.UpdateRegistrationKeyRequest) (*corepb.Registration, error) {
if core.IsAnyNilOrZero(req.RegistrationID, req.Jwk) {

View File

@ -335,8 +335,8 @@ func initAuthorities(t *testing.T) (*DummyValidationAuthority, sapb.StorageAutho
},
blog.NewMock())
test.AssertNotError(t, err, "Couldn't create PA")
err = pa.LoadHostnamePolicyFile("../test/hostname-policy.yaml")
test.AssertNotError(t, err, "Couldn't set hostname policy")
err = pa.LoadIdentPolicyFile("../test/ident-policy.yaml")
test.AssertNotError(t, err, "Couldn't set identifier policy")
stats := metrics.NoopRegisterer
@ -473,12 +473,10 @@ func TestValidateContacts(t *testing.T) {
func TestNewRegistration(t *testing.T) {
_, sa, ra, _, _, cleanUp := initAuthorities(t)
defer cleanUp()
mailto := "mailto:foo@letsencrypt.org"
acctKeyB, err := AccountKeyB.MarshalJSON()
test.AssertNotError(t, err, "failed to marshal account key")
input := &corepb.Registration{
Contact: []string{mailto},
Key: acctKeyB,
Key: acctKeyB,
}
result, err := ra.NewRegistration(ctx, input)
@ -486,7 +484,6 @@ func TestNewRegistration(t *testing.T) {
t.Fatalf("could not create new registration: %s", err)
}
test.AssertByteEquals(t, result.Key, acctKeyB)
test.Assert(t, len(result.Contact) == 0, "Wrong number of contacts")
test.Assert(t, result.Agreement == "", "Agreement didn't default empty")
reg, err := sa.GetRegistration(ctx, &sapb.RegistrationID{Id: result.Id})
@ -509,8 +506,7 @@ func TestNewRegistrationSAFailure(t *testing.T) {
acctKeyB, err := AccountKeyB.MarshalJSON()
test.AssertNotError(t, err, "failed to marshal account key")
input := corepb.Registration{
Contact: []string{"mailto:test@example.com"},
Key: acctKeyB,
Key: acctKeyB,
}
result, err := ra.NewRegistration(ctx, &input)
if err == nil {
@ -521,13 +517,11 @@ func TestNewRegistrationSAFailure(t *testing.T) {
func TestNewRegistrationNoFieldOverwrite(t *testing.T) {
_, _, ra, _, _, cleanUp := initAuthorities(t)
defer cleanUp()
mailto := "mailto:foo@letsencrypt.org"
acctKeyC, err := AccountKeyC.MarshalJSON()
test.AssertNotError(t, err, "failed to marshal account key")
input := &corepb.Registration{
Id: 23,
Key: acctKeyC,
Contact: []string{mailto},
Agreement: "I agreed",
}
@ -541,12 +535,10 @@ func TestNewRegistrationNoFieldOverwrite(t *testing.T) {
func TestNewRegistrationBadKey(t *testing.T) {
_, _, ra, _, _, cleanUp := initAuthorities(t)
defer cleanUp()
mailto := "mailto:foo@letsencrypt.org"
shortKey, err := ShortKey.MarshalJSON()
test.AssertNotError(t, err, "failed to marshal account key")
input := &corepb.Registration{
Contact: []string{mailto},
Key: shortKey,
Key: shortKey,
}
_, err = ra.NewRegistration(ctx, input)
test.AssertError(t, err, "Should have rejected authorization with short key")
@ -2913,8 +2905,8 @@ func TestFinalizeOrderDisabledChallenge(t *testing.T) {
},
ra.log)
test.AssertNotError(t, err, "creating test PA")
err = pa.LoadHostnamePolicyFile("../test/hostname-policy.yaml")
test.AssertNotError(t, err, "loading test hostname policy")
err = pa.LoadIdentPolicyFile("../test/ident-policy.yaml")
test.AssertNotError(t, err, "loading test identifier policy")
ra.PA = pa
// Now finalizing this order should fail
@ -4190,34 +4182,17 @@ type NoUpdateSA struct {
sapb.StorageAuthorityClient
}
func (sa *NoUpdateSA) UpdateRegistrationContact(_ context.Context, _ *sapb.UpdateRegistrationContactRequest, _ ...grpc.CallOption) (*corepb.Registration, error) {
return nil, fmt.Errorf("UpdateRegistrationContact() is mocked to always error")
}
func (sa *NoUpdateSA) UpdateRegistrationKey(_ context.Context, _ *sapb.UpdateRegistrationKeyRequest, _ ...grpc.CallOption) (*corepb.Registration, error) {
return nil, fmt.Errorf("UpdateRegistrationKey() is mocked to always error")
}
// mockSARecordingRegistration tests UpdateRegistrationContact and UpdateRegistrationKey.
// mockSARecordingRegistration tests UpdateRegistrationKey.
type mockSARecordingRegistration struct {
sapb.StorageAuthorityClient
providedRegistrationID int64
providedContacts []string
providedJwk []byte
}
// UpdateRegistrationContact records the registration ID and updated contacts
// (optional) provided.
func (sa *mockSARecordingRegistration) UpdateRegistrationContact(ctx context.Context, req *sapb.UpdateRegistrationContactRequest, _ ...grpc.CallOption) (*corepb.Registration, error) {
sa.providedRegistrationID = req.RegistrationID
sa.providedContacts = req.Contacts
return &corepb.Registration{
Id: req.RegistrationID,
Contact: req.Contacts,
}, nil
}
// UpdateRegistrationKey records the registration ID and updated key provided.
func (sa *mockSARecordingRegistration) UpdateRegistrationKey(ctx context.Context, req *sapb.UpdateRegistrationKeyRequest, _ ...grpc.CallOption) (*corepb.Registration, error) {
sa.providedRegistrationID = req.RegistrationID
@ -4229,62 +4204,6 @@ func (sa *mockSARecordingRegistration) UpdateRegistrationKey(ctx context.Context
}, nil
}
// TestUpdateRegistrationContact tests that the RA's UpdateRegistrationContact
// method correctly: requires a registration ID; validates the contact provided;
// does not require a contact; passes the requested registration ID and contact
// to the SA; passes the updated Registration back to the caller; and can return
// an error.
func TestUpdateRegistrationContact(t *testing.T) {
_, _, ra, _, _, cleanUp := initAuthorities(t)
defer cleanUp()
expectRegID := int64(1)
expectContacts := []string{"mailto:test@contoso.com"}
mockSA := mockSARecordingRegistration{}
ra.SA = &mockSA
_, err := ra.UpdateRegistrationContact(context.Background(), &rapb.UpdateRegistrationContactRequest{})
test.AssertError(t, err, "should not have been able to update registration contact without a registration ID")
test.AssertContains(t, err.Error(), "incomplete gRPC request message")
_, err = ra.UpdateRegistrationContact(context.Background(), &rapb.UpdateRegistrationContactRequest{
RegistrationID: expectRegID,
Contacts: []string{"tel:+44123"},
})
test.AssertError(t, err, "should not have been able to update registration contact to an invalid contact")
test.AssertContains(t, err.Error(), "invalid contact")
res, err := ra.UpdateRegistrationContact(context.Background(), &rapb.UpdateRegistrationContactRequest{
RegistrationID: expectRegID,
})
test.AssertNotError(t, err, "should have been able to update registration with a blank contact")
test.AssertEquals(t, res.Id, expectRegID)
test.AssertEquals(t, mockSA.providedRegistrationID, expectRegID)
test.AssertDeepEquals(t, res.Contact, []string(nil))
test.AssertDeepEquals(t, mockSA.providedContacts, []string(nil))
res, err = ra.UpdateRegistrationContact(context.Background(), &rapb.UpdateRegistrationContactRequest{
RegistrationID: expectRegID,
Contacts: expectContacts,
})
test.AssertNotError(t, err, "should have been able to update registration with a populated contact")
test.AssertEquals(t, res.Id, expectRegID)
test.AssertEquals(t, mockSA.providedRegistrationID, expectRegID)
test.AssertDeepEquals(t, res.Contact, expectContacts)
test.AssertDeepEquals(t, mockSA.providedContacts, expectContacts)
// Switch to a mock SA that will always error if UpdateRegistrationContact()
// is called.
ra.SA = &NoUpdateSA{}
_, err = ra.UpdateRegistrationContact(context.Background(), &rapb.UpdateRegistrationContactRequest{
RegistrationID: expectRegID,
Contacts: expectContacts,
})
test.AssertError(t, err, "should have received an error from the SA")
test.AssertContains(t, err.Error(), "failed to update registration contact")
test.AssertContains(t, err.Error(), "mocked to always error")
}
// TestUpdateRegistrationKey tests that the RA's UpdateRegistrationKey method
// correctly requires a registration ID and key, passes them to the SA, and
// passes the updated Registration back to the caller.

View File

@ -10,7 +10,7 @@ import (
// returns a Decision struct with the result of the decision and the updated
// TAT. The cost must be 0 or greater and <= the burst capacity of the limit.
func maybeSpend(clk clock.Clock, txn Transaction, tat time.Time) *Decision {
if txn.cost < 0 || txn.cost > txn.limit.burst {
if txn.cost < 0 || txn.cost > txn.limit.Burst {
// The condition above is the union of the conditions checked in Check
// and Spend methods of Limiter. If this panic is reached, it means that
// the caller has introduced a bug.
@ -67,7 +67,7 @@ func maybeSpend(clk clock.Clock, txn Transaction, tat time.Time) *Decision {
// or greater. A cost will only be refunded up to the burst capacity of the
// limit. A partial refund is still considered successful.
func maybeRefund(clk clock.Clock, txn Transaction, tat time.Time) *Decision {
if txn.cost < 0 || txn.cost > txn.limit.burst {
if txn.cost < 0 || txn.cost > txn.limit.Burst {
// The condition above is checked in the Refund method of Limiter. If
// this panic is reached, it means that the caller has introduced a bug.
panic("invalid cost for maybeRefund")
@ -80,7 +80,7 @@ func maybeRefund(clk clock.Clock, txn Transaction, tat time.Time) *Decision {
// The TAT is in the past, therefore the bucket is full.
return &Decision{
allowed: false,
remaining: txn.limit.burst,
remaining: txn.limit.Burst,
retryIn: time.Duration(0),
resetIn: time.Duration(0),
newTAT: tat,

View File

@ -12,7 +12,7 @@ import (
func TestDecide(t *testing.T) {
clk := clock.NewFake()
limit := &limit{burst: 10, count: 1, period: config.Duration{Duration: time.Second}}
limit := &Limit{Burst: 10, Count: 1, Period: config.Duration{Duration: time.Second}}
limit.precompute()
// Begin by using 1 of our 10 requests.
@ -139,7 +139,7 @@ func TestDecide(t *testing.T) {
func TestMaybeRefund(t *testing.T) {
clk := clock.NewFake()
limit := &limit{burst: 10, count: 1, period: config.Duration{Duration: time.Second}}
limit := &Limit{Burst: 10, Count: 1, Period: config.Duration{Duration: time.Second}}
limit.precompute()
// Begin by using 1 of our 10 requests.

View File

@ -1,10 +1,13 @@
package ratelimits
import (
"encoding/csv"
"errors"
"fmt"
"net/netip"
"os"
"sort"
"strconv"
"strings"
"github.com/letsencrypt/boulder/config"
@ -38,26 +41,32 @@ type LimitConfig struct {
type LimitConfigs map[string]*LimitConfig
// limit defines the configuration for a rate limit or a rate limit override.
// Limit defines the configuration for a rate limit or a rate limit override.
//
// The zero value of this struct is invalid, because some of the fields must
// be greater than zero.
type limit struct {
// burst specifies maximum concurrent allowed requests at any given time. It
// The zero value of this struct is invalid, because some of the fields must be
// greater than zero. It and several of its fields are exported to support admin
// tooling used during the migration from overrides.yaml to the overrides
// database table.
type Limit struct {
// Burst specifies maximum concurrent allowed requests at any given time. It
// must be greater than zero.
burst int64
Burst int64
// count is the number of requests allowed per period. It must be greater
// Count is the number of requests allowed per period. It must be greater
// than zero.
count int64
Count int64
// period is the duration of time in which the count (of requests) is
// Period is the duration of time in which the count (of requests) is
// allowed. It must be greater than zero.
period config.Duration
Period config.Duration
// name is the name of the limit. It must be one of the Name enums defined
// Name is the name of the limit. It must be one of the Name enums defined
// in this package.
name Name
Name Name
// Comment is an optional field that can be used to provide additional
// context for an override. It is not used for default limits.
Comment string
// emissionInterval is the interval, in nanoseconds, at which tokens are
// added to a bucket (period / count). This is also the steady-state rate at
@ -76,25 +85,25 @@ type limit struct {
}
// precompute calculates the emissionInterval and burstOffset for the limit.
func (l *limit) precompute() {
l.emissionInterval = l.period.Nanoseconds() / l.count
l.burstOffset = l.emissionInterval * l.burst
func (l *Limit) precompute() {
l.emissionInterval = l.Period.Nanoseconds() / l.Count
l.burstOffset = l.emissionInterval * l.Burst
}
func validateLimit(l *limit) error {
if l.burst <= 0 {
return fmt.Errorf("invalid burst '%d', must be > 0", l.burst)
func ValidateLimit(l *Limit) error {
if l.Burst <= 0 {
return fmt.Errorf("invalid burst '%d', must be > 0", l.Burst)
}
if l.count <= 0 {
return fmt.Errorf("invalid count '%d', must be > 0", l.count)
if l.Count <= 0 {
return fmt.Errorf("invalid count '%d', must be > 0", l.Count)
}
if l.period.Duration <= 0 {
return fmt.Errorf("invalid period '%s', must be > 0", l.period)
if l.Period.Duration <= 0 {
return fmt.Errorf("invalid period '%s', must be > 0", l.Period)
}
return nil
}
type limits map[string]*limit
type Limits map[string]*Limit
// loadDefaults marshals the defaults YAML file at path into a map of limits.
func loadDefaults(path string) (LimitConfigs, error) {
@ -149,9 +158,9 @@ func parseOverrideNameId(key string) (Name, string, error) {
return Unknown, "", fmt.Errorf("empty name in override %q, must be formatted 'name:id'", key)
}
name, ok := stringToName[nameStr]
name, ok := StringToName[nameStr]
if !ok {
return Unknown, "", fmt.Errorf("unrecognized name %q in override limit %q, must be one of %v", nameStr, key, limitNames)
return Unknown, "", fmt.Errorf("unrecognized name %q in override limit %q, must be one of %v", nameStr, key, LimitNames)
}
id := nameAndId[1]
if id == "" {
@ -160,37 +169,52 @@ func parseOverrideNameId(key string) (Name, string, error) {
return name, id, nil
}
// parseOverrideNameEnumId is like parseOverrideNameId, but it expects the
// key to be formatted as 'name:id', where 'name' is a Name enum string and 'id'
// is a string identifier. It returns an error if either part is missing or invalid.
func parseOverrideNameEnumId(key string) (Name, string, error) {
if !strings.Contains(key, ":") {
// Avoids a potential panic in strings.SplitN below.
return Unknown, "", fmt.Errorf("invalid override %q, must be formatted 'name:id'", key)
}
nameStrAndId := strings.SplitN(key, ":", 2)
if len(nameStrAndId) != 2 {
return Unknown, "", fmt.Errorf("invalid override %q, must be formatted 'name:id'", key)
}
nameInt, err := strconv.Atoi(nameStrAndId[0])
if err != nil {
return Unknown, "", fmt.Errorf("invalid name %q in override limit %q, must be an integer", nameStrAndId[0], key)
}
name := Name(nameInt)
if !name.isValid() {
return Unknown, "", fmt.Errorf("invalid name %q in override limit %q, must be one of %v", nameStrAndId[0], key, LimitNames)
}
id := nameStrAndId[1]
if id == "" {
return Unknown, "", fmt.Errorf("empty id in override %q, must be formatted 'name:id'", key)
}
return name, id, nil
}
// parseOverrideLimits validates a YAML list of override limits. It must be
// formatted as a list of maps, where each map has a single key representing the
// limit name and a value that is a map containing the limit fields and an
// additional 'ids' field that is a list of ids that this override applies to.
func parseOverrideLimits(newOverridesYAML overridesYAML) (limits, error) {
parsed := make(limits)
func parseOverrideLimits(newOverridesYAML overridesYAML) (Limits, error) {
parsed := make(Limits)
for _, ov := range newOverridesYAML {
for k, v := range ov {
name, ok := stringToName[k]
name, ok := StringToName[k]
if !ok {
return nil, fmt.Errorf("unrecognized name %q in override limit, must be one of %v", k, limitNames)
}
lim := &limit{
burst: v.Burst,
count: v.Count,
period: v.Period,
name: name,
isOverride: true,
}
lim.precompute()
err := validateLimit(lim)
if err != nil {
return nil, fmt.Errorf("validating override limit %q: %w", k, err)
return nil, fmt.Errorf("unrecognized name %q in override limit, must be one of %v", k, LimitNames)
}
for _, entry := range v.Ids {
id := entry.Id
err = validateIdForName(name, id)
err := validateIdForName(name, id)
if err != nil {
return nil, fmt.Errorf(
"validating name %s and id %q for override limit %q: %w", name, id, k, err)
@ -204,7 +228,7 @@ func parseOverrideLimits(newOverridesYAML overridesYAML) (limits, error) {
// (IPv6) prefixes in CIDR notation.
ip, err := netip.ParseAddr(id)
if err == nil {
prefix, err := coveringPrefix(ip)
prefix, err := coveringIPPrefix(name, ip)
if err != nil {
return nil, fmt.Errorf(
"computing prefix for IP address %q: %w", id, err)
@ -214,16 +238,22 @@ func parseOverrideLimits(newOverridesYAML overridesYAML) (limits, error) {
case CertificatesPerFQDNSet:
// Compute the hash of a comma-separated list of identifier
// values.
var idents identifier.ACMEIdentifiers
for _, value := range strings.Split(id, ",") {
ip, err := netip.ParseAddr(value)
if err == nil {
idents = append(idents, identifier.NewIP(ip))
} else {
idents = append(idents, identifier.NewDNS(value))
}
}
id = fmt.Sprintf("%x", core.HashIdentifiers(idents))
id = fmt.Sprintf("%x", core.HashIdentifiers(identifier.FromStringSlice(strings.Split(id, ","))))
}
lim := &Limit{
Burst: v.Burst,
Count: v.Count,
Period: v.Period,
Name: name,
Comment: entry.Comment,
isOverride: true,
}
lim.precompute()
err = ValidateLimit(lim)
if err != nil {
return nil, fmt.Errorf("validating override limit %q: %w", k, err)
}
parsed[joinWithColon(name.EnumString(), id)] = lim
@ -234,23 +264,23 @@ func parseOverrideLimits(newOverridesYAML overridesYAML) (limits, error) {
}
// parseDefaultLimits validates a map of default limits and rekeys it by 'Name'.
func parseDefaultLimits(newDefaultLimits LimitConfigs) (limits, error) {
parsed := make(limits)
func parseDefaultLimits(newDefaultLimits LimitConfigs) (Limits, error) {
parsed := make(Limits)
for k, v := range newDefaultLimits {
name, ok := stringToName[k]
name, ok := StringToName[k]
if !ok {
return nil, fmt.Errorf("unrecognized name %q in default limit, must be one of %v", k, limitNames)
return nil, fmt.Errorf("unrecognized name %q in default limit, must be one of %v", k, LimitNames)
}
lim := &limit{
burst: v.Burst,
count: v.Count,
period: v.Period,
name: name,
lim := &Limit{
Burst: v.Burst,
Count: v.Count,
Period: v.Period,
Name: name,
}
err := validateLimit(lim)
err := ValidateLimit(lim)
if err != nil {
return nil, fmt.Errorf("parsing default limit %q: %w", k, err)
}
@ -263,10 +293,10 @@ func parseDefaultLimits(newDefaultLimits LimitConfigs) (limits, error) {
type limitRegistry struct {
// defaults stores default limits by 'name'.
defaults limits
defaults Limits
// overrides stores override limits by 'name:id'.
overrides limits
overrides Limits
}
func newLimitRegistryFromFiles(defaults, overrides string) (*limitRegistry, error) {
@ -308,7 +338,7 @@ func newLimitRegistry(defaults LimitConfigs, overrides overridesYAML) (*limitReg
// required, bucketKey is optional. If bucketkey is empty, the default for the
// limit specified by name is returned. If no default limit exists for the
// specified name, errLimitDisabled is returned.
func (l *limitRegistry) getLimit(name Name, bucketKey string) (*limit, error) {
func (l *limitRegistry) getLimit(name Name, bucketKey string) (*Limit, error) {
if !name.isValid() {
// This should never happen. Callers should only be specifying the limit
// Name enums defined in this package.
@ -327,3 +357,103 @@ func (l *limitRegistry) getLimit(name Name, bucketKey string) (*limit, error) {
}
return nil, errLimitDisabled
}
// LoadOverridesByBucketKey loads the overrides YAML at the supplied path,
// parses it with the existing helpers, and returns the resulting limits map
// keyed by "<name>:<id>". This function is exported to support admin tooling
// used during the migration from overrides.yaml to the overrides database
// table.
func LoadOverridesByBucketKey(path string) (Limits, error) {
ovs, err := loadOverrides(path)
if err != nil {
return nil, err
}
return parseOverrideLimits(ovs)
}
// DumpOverrides writes the provided overrides to CSV at the supplied path. Each
// override is written as a single row, one per ID. Rows are sorted in the
// following order:
// - Name (ascending)
// - Count (descending)
// - Burst (descending)
// - Period (ascending)
// - Comment (ascending)
// - ID (ascending)
//
// This function supports admin tooling that routinely exports the overrides
// table for investigation or auditing.
func DumpOverrides(path string, overrides Limits) error {
type row struct {
name string
id string
count int64
burst int64
period string
comment string
}
var rows []row
for bucketKey, limit := range overrides {
name, id, err := parseOverrideNameEnumId(bucketKey)
if err != nil {
return err
}
rows = append(rows, row{
name: name.String(),
id: id,
count: limit.Count,
burst: limit.Burst,
period: limit.Period.Duration.String(),
comment: limit.Comment,
})
}
sort.Slice(rows, func(i, j int) bool {
// Sort by limit name in ascending order.
if rows[i].name != rows[j].name {
return rows[i].name < rows[j].name
}
// Sort by count in descending order (higher counts first).
if rows[i].count != rows[j].count {
return rows[i].count > rows[j].count
}
// Sort by burst in descending order (higher bursts first).
if rows[i].burst != rows[j].burst {
return rows[i].burst > rows[j].burst
}
// Sort by period in ascending order (shorter durations first).
if rows[i].period != rows[j].period {
return rows[i].period < rows[j].period
}
// Sort by comment in ascending order.
if rows[i].comment != rows[j].comment {
return rows[i].comment < rows[j].comment
}
// Sort by ID in ascending order.
return rows[i].id < rows[j].id
})
f, err := os.Create(path)
if err != nil {
return err
}
defer f.Close()
w := csv.NewWriter(f)
err = w.Write([]string{"name", "id", "count", "burst", "period", "comment"})
if err != nil {
return err
}
for _, r := range rows {
err := w.Write([]string{r.name, r.id, strconv.FormatInt(r.count, 10), strconv.FormatInt(r.burst, 10), r.period, r.comment})
if err != nil {
return err
}
}
w.Flush()
return w.Error()
}

View File

@ -3,6 +3,8 @@ package ratelimits
import (
"net/netip"
"os"
"path/filepath"
"strings"
"testing"
"time"
@ -15,7 +17,7 @@ import (
// parseDefaultLimits to handle a YAML file.
//
// TODO(#7901): Update the tests to test these functions individually.
func loadAndParseDefaultLimits(path string) (limits, error) {
func loadAndParseDefaultLimits(path string) (Limits, error) {
fromFile, err := loadDefaults(path)
if err != nil {
return nil, err
@ -28,7 +30,7 @@ func loadAndParseDefaultLimits(path string) (limits, error) {
// parseOverrideLimits to handle a YAML file.
//
// TODO(#7901): Update the tests to test these functions individually.
func loadAndParseOverrideLimits(path string) (limits, error) {
func loadAndParseOverrideLimits(path string) (Limits, error) {
fromFile, err := loadOverrides(path)
if err != nil {
return nil, err
@ -69,17 +71,79 @@ func TestParseOverrideNameId(t *testing.T) {
test.AssertError(t, err, "invalid enum")
}
func TestParseOverrideNameEnumId(t *testing.T) {
t.Parallel()
tests := []struct {
name string
input string
wantLimit Name
wantId string
expectError bool
}{
{
name: "valid IPv4 address",
input: NewRegistrationsPerIPAddress.EnumString() + ":10.0.0.1",
wantLimit: NewRegistrationsPerIPAddress,
wantId: "10.0.0.1",
expectError: false,
},
{
name: "valid IPv6 address range",
input: NewRegistrationsPerIPv6Range.EnumString() + ":2001:0db8:0000::/48",
wantLimit: NewRegistrationsPerIPv6Range,
wantId: "2001:0db8:0000::/48",
expectError: false,
},
{
name: "missing colon",
input: NewRegistrationsPerIPAddress.EnumString() + "10.0.0.1",
expectError: true,
},
{
name: "empty string",
input: "",
expectError: true,
},
{
name: "only a colon",
input: NewRegistrationsPerIPAddress.EnumString() + ":",
expectError: true,
},
{
name: "invalid enum",
input: "lol:noexist",
expectError: true,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
limit, id, err := parseOverrideNameEnumId(tc.input)
if tc.expectError {
if err == nil {
t.Errorf("expected error for input %q, but got none", tc.input)
}
} else {
test.AssertNotError(t, err, tc.name)
test.AssertEquals(t, limit, tc.wantLimit)
test.AssertEquals(t, id, tc.wantId)
}
})
}
}
func TestValidateLimit(t *testing.T) {
err := validateLimit(&limit{burst: 1, count: 1, period: config.Duration{Duration: time.Second}})
err := ValidateLimit(&Limit{Burst: 1, Count: 1, Period: config.Duration{Duration: time.Second}})
test.AssertNotError(t, err, "valid limit")
// All of the following are invalid.
for _, l := range []*limit{
{burst: 0, count: 1, period: config.Duration{Duration: time.Second}},
{burst: 1, count: 0, period: config.Duration{Duration: time.Second}},
{burst: 1, count: 1, period: config.Duration{Duration: 0}},
for _, l := range []*Limit{
{Burst: 0, Count: 1, Period: config.Duration{Duration: time.Second}},
{Burst: 1, Count: 0, Period: config.Duration{Duration: time.Second}},
{Burst: 1, Count: 1, Period: config.Duration{Duration: 0}},
} {
err = validateLimit(l)
err = ValidateLimit(l)
test.AssertError(t, err, "limit should be invalid")
}
}
@ -89,29 +153,29 @@ func TestLoadAndParseOverrideLimits(t *testing.T) {
l, err := loadAndParseOverrideLimits("testdata/working_override.yml")
test.AssertNotError(t, err, "valid single override limit")
expectKey := joinWithColon(NewRegistrationsPerIPAddress.EnumString(), "64.112.117.1")
test.AssertEquals(t, l[expectKey].burst, int64(40))
test.AssertEquals(t, l[expectKey].count, int64(40))
test.AssertEquals(t, l[expectKey].period.Duration, time.Second)
test.AssertEquals(t, l[expectKey].Burst, int64(40))
test.AssertEquals(t, l[expectKey].Count, int64(40))
test.AssertEquals(t, l[expectKey].Period.Duration, time.Second)
// Load single valid override limit with a 'domainOrCIDR' Id.
l, err = loadAndParseOverrideLimits("testdata/working_override_regid_domainorcidr.yml")
test.AssertNotError(t, err, "valid single override limit with Id of regId:domainOrCIDR")
expectKey = joinWithColon(CertificatesPerDomain.EnumString(), "example.com")
test.AssertEquals(t, l[expectKey].burst, int64(40))
test.AssertEquals(t, l[expectKey].count, int64(40))
test.AssertEquals(t, l[expectKey].period.Duration, time.Second)
test.AssertEquals(t, l[expectKey].Burst, int64(40))
test.AssertEquals(t, l[expectKey].Count, int64(40))
test.AssertEquals(t, l[expectKey].Period.Duration, time.Second)
// Load multiple valid override limits with 'regId' Ids.
l, err = loadAndParseOverrideLimits("testdata/working_overrides.yml")
test.AssertNotError(t, err, "multiple valid override limits")
expectKey1 := joinWithColon(NewRegistrationsPerIPAddress.EnumString(), "64.112.117.1")
test.AssertEquals(t, l[expectKey1].burst, int64(40))
test.AssertEquals(t, l[expectKey1].count, int64(40))
test.AssertEquals(t, l[expectKey1].period.Duration, time.Second)
test.AssertEquals(t, l[expectKey1].Burst, int64(40))
test.AssertEquals(t, l[expectKey1].Count, int64(40))
test.AssertEquals(t, l[expectKey1].Period.Duration, time.Second)
expectKey2 := joinWithColon(NewRegistrationsPerIPv6Range.EnumString(), "2602:80a:6000::/48")
test.AssertEquals(t, l[expectKey2].burst, int64(50))
test.AssertEquals(t, l[expectKey2].count, int64(50))
test.AssertEquals(t, l[expectKey2].period.Duration, time.Second*2)
test.AssertEquals(t, l[expectKey2].Burst, int64(50))
test.AssertEquals(t, l[expectKey2].Count, int64(50))
test.AssertEquals(t, l[expectKey2].Period.Duration, time.Second*2)
// Load multiple valid override limits with 'fqdnSet' Ids, as follows:
// - CertificatesPerFQDNSet:example.com
@ -128,18 +192,18 @@ func TestLoadAndParseOverrideLimits(t *testing.T) {
l, err = loadAndParseOverrideLimits("testdata/working_overrides_regid_fqdnset.yml")
test.AssertNotError(t, err, "multiple valid override limits with 'fqdnSet' Ids")
test.AssertEquals(t, l[entryKey1].burst, int64(40))
test.AssertEquals(t, l[entryKey1].count, int64(40))
test.AssertEquals(t, l[entryKey1].period.Duration, time.Second)
test.AssertEquals(t, l[entryKey2].burst, int64(50))
test.AssertEquals(t, l[entryKey2].count, int64(50))
test.AssertEquals(t, l[entryKey2].period.Duration, time.Second*2)
test.AssertEquals(t, l[entryKey3].burst, int64(60))
test.AssertEquals(t, l[entryKey3].count, int64(60))
test.AssertEquals(t, l[entryKey3].period.Duration, time.Second*3)
test.AssertEquals(t, l[entryKey4].burst, int64(60))
test.AssertEquals(t, l[entryKey4].count, int64(60))
test.AssertEquals(t, l[entryKey4].period.Duration, time.Second*4)
test.AssertEquals(t, l[entryKey1].Burst, int64(40))
test.AssertEquals(t, l[entryKey1].Count, int64(40))
test.AssertEquals(t, l[entryKey1].Period.Duration, time.Second)
test.AssertEquals(t, l[entryKey2].Burst, int64(50))
test.AssertEquals(t, l[entryKey2].Count, int64(50))
test.AssertEquals(t, l[entryKey2].Period.Duration, time.Second*2)
test.AssertEquals(t, l[entryKey3].Burst, int64(60))
test.AssertEquals(t, l[entryKey3].Count, int64(60))
test.AssertEquals(t, l[entryKey3].Period.Duration, time.Second*3)
test.AssertEquals(t, l[entryKey4].Burst, int64(60))
test.AssertEquals(t, l[entryKey4].Count, int64(60))
test.AssertEquals(t, l[entryKey4].Period.Duration, time.Second*4)
// Path is empty string.
_, err = loadAndParseOverrideLimits("")
@ -186,19 +250,19 @@ func TestLoadAndParseDefaultLimits(t *testing.T) {
// Load a single valid default limit.
l, err := loadAndParseDefaultLimits("testdata/working_default.yml")
test.AssertNotError(t, err, "valid single default limit")
test.AssertEquals(t, l[NewRegistrationsPerIPAddress.EnumString()].burst, int64(20))
test.AssertEquals(t, l[NewRegistrationsPerIPAddress.EnumString()].count, int64(20))
test.AssertEquals(t, l[NewRegistrationsPerIPAddress.EnumString()].period.Duration, time.Second)
test.AssertEquals(t, l[NewRegistrationsPerIPAddress.EnumString()].Burst, int64(20))
test.AssertEquals(t, l[NewRegistrationsPerIPAddress.EnumString()].Count, int64(20))
test.AssertEquals(t, l[NewRegistrationsPerIPAddress.EnumString()].Period.Duration, time.Second)
// Load multiple valid default limits.
l, err = loadAndParseDefaultLimits("testdata/working_defaults.yml")
test.AssertNotError(t, err, "multiple valid default limits")
test.AssertEquals(t, l[NewRegistrationsPerIPAddress.EnumString()].burst, int64(20))
test.AssertEquals(t, l[NewRegistrationsPerIPAddress.EnumString()].count, int64(20))
test.AssertEquals(t, l[NewRegistrationsPerIPAddress.EnumString()].period.Duration, time.Second)
test.AssertEquals(t, l[NewRegistrationsPerIPv6Range.EnumString()].burst, int64(30))
test.AssertEquals(t, l[NewRegistrationsPerIPv6Range.EnumString()].count, int64(30))
test.AssertEquals(t, l[NewRegistrationsPerIPv6Range.EnumString()].period.Duration, time.Second*2)
test.AssertEquals(t, l[NewRegistrationsPerIPAddress.EnumString()].Burst, int64(20))
test.AssertEquals(t, l[NewRegistrationsPerIPAddress.EnumString()].Count, int64(20))
test.AssertEquals(t, l[NewRegistrationsPerIPAddress.EnumString()].Period.Duration, time.Second)
test.AssertEquals(t, l[NewRegistrationsPerIPv6Range.EnumString()].Burst, int64(30))
test.AssertEquals(t, l[NewRegistrationsPerIPv6Range.EnumString()].Count, int64(30))
test.AssertEquals(t, l[NewRegistrationsPerIPv6Range.EnumString()].Period.Duration, time.Second*2)
// Path is empty string.
_, err = loadAndParseDefaultLimits("")
@ -230,3 +294,146 @@ func TestLoadAndParseDefaultLimits(t *testing.T) {
test.AssertError(t, err, "multiple default limits, one is bad")
test.Assert(t, !os.IsNotExist(err), "test file should exist")
}
func TestLoadAndDumpOverrides(t *testing.T) {
t.Parallel()
input := `
- CertificatesPerDomain:
burst: 5000
count: 5000
period: 168h0m0s
ids:
- id: example.com
comment: IN-10057
- id: example.net
comment: IN-10057
- CertificatesPerDomain:
burst: 300
count: 300
period: 168h0m0s
ids:
- id: example.org
comment: IN-10057
- CertificatesPerDomainPerAccount:
burst: 12000
count: 12000
period: 168h0m0s
ids:
- id: "123456789"
comment: Affluent (IN-8322)
- CertificatesPerDomainPerAccount:
burst: 6000
count: 6000
period: 168h0m0s
ids:
- id: "543219876"
comment: Affluent (IN-8322)
- id: "987654321"
comment: Affluent (IN-8322)
- CertificatesPerFQDNSet:
burst: 50
count: 50
period: 168h0m0s
ids:
- id: example.co.uk,example.cn
comment: IN-6843
- CertificatesPerFQDNSet:
burst: 24
count: 24
period: 168h0m0s
ids:
- id: example.org,example.com,example.net
comment: IN-6006
- FailedAuthorizationsPerDomainPerAccount:
burst: 250
count: 250
period: 1h0m0s
ids:
- id: "123456789"
comment: Digital Lake (IN-6736)
- FailedAuthorizationsPerDomainPerAccount:
burst: 50
count: 50
period: 1h0m0s
ids:
- id: "987654321"
comment: Digital Lake (IN-6856)
- FailedAuthorizationsPerDomainPerAccount:
burst: 10
count: 10
period: 1h0m0s
ids:
- id: "543219876"
comment: Big Mart (IN-6949)
- NewOrdersPerAccount:
burst: 3000
count: 3000
period: 3h0m0s
ids:
- id: "123456789"
comment: Galaxy Hoster (IN-8180)
- NewOrdersPerAccount:
burst: 1000
count: 1000
period: 3h0m0s
ids:
- id: "543219876"
comment: Big Mart (IN-8180)
- id: "987654321"
comment: Buy More (IN-10057)
- NewRegistrationsPerIPAddress:
burst: 100000
count: 100000
period: 3h0m0s
ids:
- id: 2600:1f1c:5e0:e702:ca06:d2a3:c7ce:a02e
comment: example.org IN-2395
- id: 55.66.77.88
comment: example.org IN-2395
- NewRegistrationsPerIPAddress:
burst: 200
count: 200
period: 3h0m0s
ids:
- id: 11.22.33.44
comment: example.net (IN-1583)`
expectCSV := `
name,id,count,burst,period,comment
CertificatesPerDomain,example.com,5000,5000,168h0m0s,IN-10057
CertificatesPerDomain,example.net,5000,5000,168h0m0s,IN-10057
CertificatesPerDomain,example.org,300,300,168h0m0s,IN-10057
CertificatesPerDomainPerAccount,123456789,12000,12000,168h0m0s,Affluent (IN-8322)
CertificatesPerDomainPerAccount,543219876,6000,6000,168h0m0s,Affluent (IN-8322)
CertificatesPerDomainPerAccount,987654321,6000,6000,168h0m0s,Affluent (IN-8322)
CertificatesPerFQDNSet,7c956936126b492845ddb48f4d220034509e7c0ad54ed2c1ba2650406846d9c3,50,50,168h0m0s,IN-6843
CertificatesPerFQDNSet,394e82811f52e2da38b970afdb21c9bc9af81060939c690183c00fce37408738,24,24,168h0m0s,IN-6006
FailedAuthorizationsPerDomainPerAccount,123456789,250,250,1h0m0s,Digital Lake (IN-6736)
FailedAuthorizationsPerDomainPerAccount,987654321,50,50,1h0m0s,Digital Lake (IN-6856)
FailedAuthorizationsPerDomainPerAccount,543219876,10,10,1h0m0s,Big Mart (IN-6949)
NewOrdersPerAccount,123456789,3000,3000,3h0m0s,Galaxy Hoster (IN-8180)
NewOrdersPerAccount,543219876,1000,1000,3h0m0s,Big Mart (IN-8180)
NewOrdersPerAccount,987654321,1000,1000,3h0m0s,Buy More (IN-10057)
NewRegistrationsPerIPAddress,2600:1f1c:5e0:e702:ca06:d2a3:c7ce:a02e,100000,100000,3h0m0s,example.org IN-2395
NewRegistrationsPerIPAddress,55.66.77.88,100000,100000,3h0m0s,example.org IN-2395
NewRegistrationsPerIPAddress,11.22.33.44,200,200,3h0m0s,example.net (IN-1583)
`
tempDir := t.TempDir()
tempFile := filepath.Join(tempDir, "overrides.yaml")
err := os.WriteFile(tempFile, []byte(input), 0644)
test.AssertNotError(t, err, "writing temp overrides.yaml")
original, err := LoadOverridesByBucketKey(tempFile)
test.AssertNotError(t, err, "loading overrides")
test.Assert(t, len(original) > 0, "expected at least one override loaded")
dumpFile := filepath.Join(tempDir, "dumped.yaml")
err = DumpOverrides(dumpFile, original)
test.AssertNotError(t, err, "dumping overrides")
dumped, err := os.ReadFile(dumpFile)
test.AssertNotError(t, err, "reading dumped overrides file")
test.AssertEquals(t, strings.TrimLeft(string(dumped), "\n"), strings.TrimLeft(expectCSV, "\n"))
}

View File

@ -104,13 +104,13 @@ func (d *Decision) Result(now time.Time) error {
// There is no case for FailedAuthorizationsForPausingPerDomainPerAccount
// because the RA will pause clients who exceed that ratelimit.
switch d.transaction.limit.name {
switch d.transaction.limit.Name {
case NewRegistrationsPerIPAddress:
return berrors.RegistrationsPerIPAddressError(
retryAfter,
"too many new registrations (%d) from this IP address in the last %s, retry after %s",
d.transaction.limit.burst,
d.transaction.limit.period.Duration,
d.transaction.limit.Burst,
d.transaction.limit.Period.Duration,
retryAfterTs,
)
@ -118,16 +118,16 @@ func (d *Decision) Result(now time.Time) error {
return berrors.RegistrationsPerIPv6RangeError(
retryAfter,
"too many new registrations (%d) from this /48 subnet of IPv6 addresses in the last %s, retry after %s",
d.transaction.limit.burst,
d.transaction.limit.period.Duration,
d.transaction.limit.Burst,
d.transaction.limit.Period.Duration,
retryAfterTs,
)
case NewOrdersPerAccount:
return berrors.NewOrdersPerAccountError(
retryAfter,
"too many new orders (%d) from this account in the last %s, retry after %s",
d.transaction.limit.burst,
d.transaction.limit.period.Duration,
d.transaction.limit.Burst,
d.transaction.limit.Period.Duration,
retryAfterTs,
)
@ -141,9 +141,9 @@ func (d *Decision) Result(now time.Time) error {
return berrors.FailedAuthorizationsPerDomainPerAccountError(
retryAfter,
"too many failed authorizations (%d) for %q in the last %s, retry after %s",
d.transaction.limit.burst,
d.transaction.limit.Burst,
identValue,
d.transaction.limit.period.Duration,
d.transaction.limit.Period.Duration,
retryAfterTs,
)
@ -157,9 +157,9 @@ func (d *Decision) Result(now time.Time) error {
return berrors.CertificatesPerDomainError(
retryAfter,
"too many certificates (%d) already issued for %q in the last %s, retry after %s",
d.transaction.limit.burst,
d.transaction.limit.Burst,
domainOrCIDR,
d.transaction.limit.period.Duration,
d.transaction.limit.Period.Duration,
retryAfterTs,
)
@ -167,8 +167,8 @@ func (d *Decision) Result(now time.Time) error {
return berrors.CertificatesPerFQDNSetError(
retryAfter,
"too many certificates (%d) already issued for this exact set of identifiers in the last %s, retry after %s",
d.transaction.limit.burst,
d.transaction.limit.period.Duration,
d.transaction.limit.Burst,
d.transaction.limit.Period.Duration,
retryAfterTs,
)
@ -346,7 +346,7 @@ func (l *Limiter) BatchSpend(ctx context.Context, txns []Transaction) (*Decision
totalLatency := l.clk.Since(start)
perTxnLatency := totalLatency / time.Duration(len(txnOutcomes))
for txn, outcome := range txnOutcomes {
l.spendLatency.WithLabelValues(txn.limit.name.String(), outcome).Observe(perTxnLatency.Seconds())
l.spendLatency.WithLabelValues(txn.limit.Name.String(), outcome).Observe(perTxnLatency.Seconds())
}
return batchDecision, nil
}

View File

@ -464,10 +464,10 @@ func TestRateLimitError(t *testing.T) {
allowed: false,
retryIn: 5 * time.Second,
transaction: Transaction{
limit: &limit{
name: NewRegistrationsPerIPAddress,
burst: 10,
period: config.Duration{Duration: time.Hour},
limit: &Limit{
Name: NewRegistrationsPerIPAddress,
Burst: 10,
Period: config.Duration{Duration: time.Hour},
},
},
},
@ -480,10 +480,10 @@ func TestRateLimitError(t *testing.T) {
allowed: false,
retryIn: 10 * time.Second,
transaction: Transaction{
limit: &limit{
name: NewRegistrationsPerIPv6Range,
burst: 5,
period: config.Duration{Duration: time.Hour},
limit: &Limit{
Name: NewRegistrationsPerIPv6Range,
Burst: 5,
Period: config.Duration{Duration: time.Hour},
},
},
},
@ -496,10 +496,10 @@ func TestRateLimitError(t *testing.T) {
allowed: false,
retryIn: 10 * time.Second,
transaction: Transaction{
limit: &limit{
name: NewOrdersPerAccount,
burst: 2,
period: config.Duration{Duration: time.Hour},
limit: &Limit{
Name: NewOrdersPerAccount,
Burst: 2,
Period: config.Duration{Duration: time.Hour},
},
},
},
@ -512,10 +512,10 @@ func TestRateLimitError(t *testing.T) {
allowed: false,
retryIn: 15 * time.Second,
transaction: Transaction{
limit: &limit{
name: FailedAuthorizationsPerDomainPerAccount,
burst: 7,
period: config.Duration{Duration: time.Hour},
limit: &Limit{
Name: FailedAuthorizationsPerDomainPerAccount,
Burst: 7,
Period: config.Duration{Duration: time.Hour},
},
bucketKey: "4:12345:example.com",
},
@ -529,10 +529,10 @@ func TestRateLimitError(t *testing.T) {
allowed: false,
retryIn: 20 * time.Second,
transaction: Transaction{
limit: &limit{
name: CertificatesPerDomain,
burst: 3,
period: config.Duration{Duration: time.Hour},
limit: &Limit{
Name: CertificatesPerDomain,
Burst: 3,
Period: config.Duration{Duration: time.Hour},
},
bucketKey: "5:example.org",
},
@ -546,10 +546,10 @@ func TestRateLimitError(t *testing.T) {
allowed: false,
retryIn: 20 * time.Second,
transaction: Transaction{
limit: &limit{
name: CertificatesPerDomainPerAccount,
burst: 3,
period: config.Duration{Duration: time.Hour},
limit: &Limit{
Name: CertificatesPerDomainPerAccount,
Burst: 3,
Period: config.Duration{Duration: time.Hour},
},
bucketKey: "6:12345678:example.net",
},
@ -563,8 +563,8 @@ func TestRateLimitError(t *testing.T) {
allowed: false,
retryIn: 30 * time.Second,
transaction: Transaction{
limit: &limit{
name: 9999999,
limit: &Limit{
Name: 9999999,
},
},
},

View File

@ -7,6 +7,7 @@ import (
"strings"
"github.com/letsencrypt/boulder/iana"
"github.com/letsencrypt/boulder/identifier"
"github.com/letsencrypt/boulder/policy"
)
@ -17,8 +18,9 @@ import (
// IMPORTANT: If you add or remove a limit Name, you MUST update:
// - the string representation of the Name in nameToString,
// - the validators for that name in validateIdForName(),
// - the transaction constructors for that name in bucket.go, and
// - the Subscriber facing error message in ErrForDecision().
// - the transaction constructors for that name in bucket.go
// - the Subscriber facing error message in ErrForDecision(), and
// - the case in BuildBucketKey() for that name.
type Name int
const (
@ -206,7 +208,7 @@ func validateRegIdIdentValue(id string) error {
// validateDomainOrCIDR validates that the provided string is either a domain
// name or an IP address. IPv6 addresses must be the lowest address in their
// /64, i.e. their last 64 bits must be zero.
func validateDomainOrCIDR(id string) error {
func validateDomainOrCIDR(limit Name, id string) error {
domainErr := policy.ValidDomain(id)
if domainErr == nil {
// This is a valid domain.
@ -222,14 +224,13 @@ func validateDomainOrCIDR(id string) error {
return fmt.Errorf("invalid IP address %q, must be in canonical form (%q)", id, ip.String())
}
prefix, prefixErr := coveringPrefix(ip)
prefix, prefixErr := coveringIPPrefix(limit, ip)
if prefixErr != nil {
return fmt.Errorf("invalid IP address %q, couldn't determine prefix: %w", id, prefixErr)
}
if prefix.Addr() != ip {
return fmt.Errorf("invalid IP address %q, must be the lowest address in its prefix (%q)", id, prefix.Addr().String())
}
return iana.IsReservedPrefix(prefix)
}
@ -237,7 +238,7 @@ func validateDomainOrCIDR(id string) error {
// 'regId:domainOrCIDR', where domainOrCIDR is either a domain name or an IP
// address. IPv6 addresses must be the lowest address in their /64, i.e. their
// last 64 bits must be zero.
func validateRegIdDomainOrCIDR(id string) error {
func validateRegIdDomainOrCIDR(limit Name, id string) error {
regIdDomainOrCIDR := strings.Split(id, ":")
if len(regIdDomainOrCIDR) != 2 {
return fmt.Errorf(
@ -248,7 +249,7 @@ func validateRegIdDomainOrCIDR(id string) error {
return fmt.Errorf(
"invalid regId, %q must be formatted 'regId:domainOrCIDR'", id)
}
err = validateDomainOrCIDR(regIdDomainOrCIDR[1])
err = validateDomainOrCIDR(limit, regIdDomainOrCIDR[1])
if err != nil {
return fmt.Errorf("invalid domainOrCIDR, %q must be formatted 'regId:domainOrCIDR': %w", id, err)
}
@ -301,7 +302,7 @@ func validateIdForName(name Name, id string) error {
case CertificatesPerDomainPerAccount:
if strings.Contains(id, ":") {
// 'enum:regId:domainOrCIDR' for transaction
return validateRegIdDomainOrCIDR(id)
return validateRegIdDomainOrCIDR(name, id)
} else {
// 'enum:regId' for overrides
return validateRegId(id)
@ -309,7 +310,7 @@ func validateIdForName(name Name, id string) error {
case CertificatesPerDomain:
// 'enum:domainOrCIDR'
return validateDomainOrCIDR(id)
return validateDomainOrCIDR(name, id)
case CertificatesPerFQDNSet:
// 'enum:fqdnSet'
@ -333,8 +334,8 @@ func validateIdForName(name Name, id string) error {
}
}
// stringToName is a map of string names to Name values.
var stringToName = func() map[string]Name {
// StringToName is a map of string names to Name values.
var StringToName = func() map[string]Name {
m := make(map[string]Name, len(nameToString))
for k, v := range nameToString {
m[v] = k
@ -342,11 +343,94 @@ var stringToName = func() map[string]Name {
return m
}()
// limitNames is a slice of all rate limit names.
var limitNames = func() []string {
// LimitNames is a slice of all rate limit names.
var LimitNames = func() []string {
names := make([]string, 0, len(nameToString))
for _, v := range nameToString {
names = append(names, v)
}
return names
}()
// BuildBucketKey builds a bucketKey for the given rate limit name from the
// provided components. It returns an error if the name is not valid or if the
// components are not valid for the given name.
func BuildBucketKey(name Name, regId int64, singleIdent identifier.ACMEIdentifier, setOfIdents identifier.ACMEIdentifiers, subscriberIP netip.Addr) (string, error) {
makeMissingErr := func(field string) error {
return fmt.Errorf("%s is required for limit %s (enum: %s)", field, name, name.EnumString())
}
switch name {
case NewRegistrationsPerIPAddress:
if !subscriberIP.IsValid() {
return "", makeMissingErr("subscriberIP")
}
return newIPAddressBucketKey(name, subscriberIP), nil
case NewRegistrationsPerIPv6Range:
if !subscriberIP.IsValid() {
return "", makeMissingErr("subscriberIP")
}
prefix, err := coveringIPPrefix(name, subscriberIP)
if err != nil {
return "", err
}
return newIPv6RangeCIDRBucketKey(name, prefix), nil
case NewOrdersPerAccount:
if regId == 0 {
return "", makeMissingErr("regId")
}
return newRegIdBucketKey(name, regId), nil
case CertificatesPerDomain:
if singleIdent.Value == "" {
return "", makeMissingErr("singleIdent")
}
coveringIdent, err := coveringIdentifier(name, singleIdent)
if err != nil {
return "", err
}
return newDomainOrCIDRBucketKey(name, coveringIdent), nil
case CertificatesPerDomainPerAccount:
if singleIdent.Value != "" {
if regId == 0 {
return "", makeMissingErr("regId")
}
// Default: use 'enum:regId:identValue' bucket key format.
coveringIdent, err := coveringIdentifier(name, singleIdent)
if err != nil {
return "", err
}
return NewRegIdIdentValueBucketKey(name, regId, coveringIdent), nil
}
if regId == 0 {
return "", makeMissingErr("regId")
}
// Override: use 'enum:regId' bucket key format.
return newRegIdBucketKey(name, regId), nil
case CertificatesPerFQDNSet:
if len(setOfIdents) == 0 {
return "", makeMissingErr("setOfIdents")
}
return newFQDNSetBucketKey(name, setOfIdents), nil
case FailedAuthorizationsPerDomainPerAccount, FailedAuthorizationsForPausingPerDomainPerAccount:
if singleIdent.Value != "" {
if regId == 0 {
return "", makeMissingErr("regId")
}
// Default: use 'enum:regId:identValue' bucket key format.
return NewRegIdIdentValueBucketKey(name, regId, singleIdent.Value), nil
}
if regId == 0 {
return "", makeMissingErr("regId")
}
// Override: use 'enum:regId' bucket key format.
return newRegIdBucketKey(name, regId), nil
}
return "", fmt.Errorf("unknown limit enum %s", name.EnumString())
}

View File

@ -2,8 +2,11 @@ package ratelimits
import (
"fmt"
"net/netip"
"strings"
"testing"
"github.com/letsencrypt/boulder/identifier"
"github.com/letsencrypt/boulder/test"
)
@ -293,3 +296,202 @@ func TestValidateIdForName(t *testing.T) {
})
}
}
func TestBuildBucketKey(t *testing.T) {
t.Parallel()
tests := []struct {
name Name
desc string
regId int64
singleIdent identifier.ACMEIdentifier
setOfIdents identifier.ACMEIdentifiers
subscriberIP netip.Addr
expectErrContains string
outputTest func(t *testing.T, key string)
}{
// NewRegistrationsPerIPAddress
{
name: NewRegistrationsPerIPAddress,
desc: "valid subscriber IPv4 address",
subscriberIP: netip.MustParseAddr("1.2.3.4"),
outputTest: func(t *testing.T, key string) {
test.AssertEquals(t, fmt.Sprintf("%d:1.2.3.4", NewRegistrationsPerIPAddress), key)
},
},
{
name: NewRegistrationsPerIPAddress,
desc: "valid subscriber IPv6 address",
subscriberIP: netip.MustParseAddr("2001:db8::1"),
outputTest: func(t *testing.T, key string) {
test.AssertEquals(t, fmt.Sprintf("%d:2001:db8::1", NewRegistrationsPerIPAddress), key)
},
},
// NewRegistrationsPerIPv6Range
{
name: NewRegistrationsPerIPv6Range,
desc: "valid subscriber IPv6 address",
subscriberIP: netip.MustParseAddr("2001:db8:abcd:12::1"),
outputTest: func(t *testing.T, key string) {
test.AssertEquals(t, fmt.Sprintf("%d:2001:db8:abcd::/48", NewRegistrationsPerIPv6Range), key)
},
},
{
name: NewRegistrationsPerIPv6Range,
desc: "subscriber IPv4 given for subscriber IPv6 range limit",
subscriberIP: netip.MustParseAddr("1.2.3.4"),
expectErrContains: "requires an IPv6 address",
},
// NewOrdersPerAccount
{
name: NewOrdersPerAccount,
desc: "valid registration ID",
regId: 1337,
outputTest: func(t *testing.T, key string) {
test.AssertEquals(t, fmt.Sprintf("%d:1337", NewOrdersPerAccount), key)
},
},
{
name: NewOrdersPerAccount,
desc: "registration ID missing",
expectErrContains: "regId is required",
},
// CertificatesPerDomain
{
name: CertificatesPerDomain,
desc: "DNS identifier to eTLD+1",
singleIdent: identifier.NewDNS("www.example.com"),
outputTest: func(t *testing.T, key string) {
test.AssertEquals(t, fmt.Sprintf("%d:example.com", CertificatesPerDomain), key)
},
},
{
name: CertificatesPerDomain,
desc: "valid IPv4 address used as identifier",
singleIdent: identifier.NewIP(netip.MustParseAddr("5.6.7.8")),
outputTest: func(t *testing.T, key string) {
test.AssertEquals(t, fmt.Sprintf("%d:5.6.7.8/32", CertificatesPerDomain), key)
},
},
{
name: CertificatesPerDomain,
desc: "valid IPv6 address used as identifier",
singleIdent: identifier.NewIP(netip.MustParseAddr("2001:db8::1")),
outputTest: func(t *testing.T, key string) {
test.AssertEquals(t, fmt.Sprintf("%d:2001:db8::/64", CertificatesPerDomain), key)
},
},
{
name: CertificatesPerDomain,
desc: "identifier missing",
expectErrContains: "singleIdent is required",
},
// CertificatesPerFQDNSet
{
name: CertificatesPerFQDNSet,
desc: "multiple valid DNS identifiers",
setOfIdents: identifier.NewDNSSlice([]string{"example.com", "example.org"}),
outputTest: func(t *testing.T, key string) {
if !strings.HasPrefix(key, fmt.Sprintf("%d:", CertificatesPerFQDNSet)) {
t.Errorf("expected key to start with %d: got %s", CertificatesPerFQDNSet, key)
}
},
},
{
name: CertificatesPerFQDNSet,
desc: "multiple valid DNS and IP identifiers",
setOfIdents: identifier.ACMEIdentifiers{identifier.NewDNS("example.net"), identifier.NewIP(netip.MustParseAddr("5.6.7.8")), identifier.NewIP(netip.MustParseAddr("2001:db8::1"))},
outputTest: func(t *testing.T, key string) {
if !strings.HasPrefix(key, fmt.Sprintf("%d:", CertificatesPerFQDNSet)) {
t.Errorf("expected key to start with %d: got %s", CertificatesPerFQDNSet, key)
}
},
},
{
name: CertificatesPerFQDNSet,
desc: "identifiers missing",
expectErrContains: "setOfIdents is required",
},
// CertificatesPerDomainPerAccount
{
name: CertificatesPerDomainPerAccount,
desc: "only registration ID",
regId: 1337,
outputTest: func(t *testing.T, key string) {
test.AssertEquals(t, fmt.Sprintf("%d:1337", CertificatesPerDomainPerAccount), key)
},
},
{
name: CertificatesPerDomainPerAccount,
desc: "registration ID and single DNS identifier provided",
regId: 1337,
singleIdent: identifier.NewDNS("example.com"),
outputTest: func(t *testing.T, key string) {
test.AssertEquals(t, fmt.Sprintf("%d:1337:example.com", CertificatesPerDomainPerAccount), key)
},
},
{
name: CertificatesPerDomainPerAccount,
desc: "single DNS identifier provided without registration ID",
singleIdent: identifier.NewDNS("example.com"),
expectErrContains: "regId is required",
},
// FailedAuthorizationsPerDomainPerAccount
{
name: FailedAuthorizationsPerDomainPerAccount,
desc: "registration ID and single DNS identifier",
regId: 1337,
singleIdent: identifier.NewDNS("example.com"),
outputTest: func(t *testing.T, key string) {
test.AssertEquals(t, fmt.Sprintf("%d:1337:example.com", FailedAuthorizationsPerDomainPerAccount), key)
},
},
{
name: FailedAuthorizationsPerDomainPerAccount,
desc: "only registration ID",
regId: 1337,
outputTest: func(t *testing.T, key string) {
test.AssertEquals(t, fmt.Sprintf("%d:1337", FailedAuthorizationsPerDomainPerAccount), key)
},
},
// FailedAuthorizationsForPausingPerDomainPerAccount
{
name: FailedAuthorizationsForPausingPerDomainPerAccount,
desc: "registration ID and single DNS identifier",
regId: 1337,
singleIdent: identifier.NewDNS("example.com"),
outputTest: func(t *testing.T, key string) {
test.AssertEquals(t, fmt.Sprintf("%d:1337:example.com", FailedAuthorizationsForPausingPerDomainPerAccount), key)
},
},
{
name: FailedAuthorizationsForPausingPerDomainPerAccount,
desc: "only registration ID",
regId: 1337,
outputTest: func(t *testing.T, key string) {
test.AssertEquals(t, fmt.Sprintf("%d:1337", FailedAuthorizationsForPausingPerDomainPerAccount), key)
},
},
}
for _, tc := range tests {
t.Run(fmt.Sprintf("%s/%s", tc.name, tc.desc), func(t *testing.T) {
t.Parallel()
key, err := BuildBucketKey(tc.name, tc.regId, tc.singleIdent, tc.setOfIdents, tc.subscriberIP)
if tc.expectErrContains != "" {
test.AssertError(t, err, "expected error")
test.AssertContains(t, err.Error(), tc.expectErrContains)
return
}
test.AssertNotError(t, err, "unexpected error")
tc.outputTest(t, key)
})
}
}

View File

@ -3,5 +3,5 @@
count: 40
period: 1s
ids:
- id: 10.0.0.2
- id: 55.66.77.88
comment: Foo

View File

@ -16,32 +16,25 @@ var ErrInvalidCost = fmt.Errorf("invalid cost, must be >= 0")
// ErrInvalidCostOverLimit indicates that the cost specified was > limit.Burst.
var ErrInvalidCostOverLimit = fmt.Errorf("invalid cost, must be <= limit.Burst")
// newIPAddressBucketKey validates and returns a bucketKey for limits that use
// newIPAddressBucketKey returns a bucketKey for limits that use
// the 'enum:ipAddress' bucket key format.
func newIPAddressBucketKey(name Name, ip netip.Addr) string { //nolint:unparam // Only one named rate limit uses this helper
func newIPAddressBucketKey(name Name, ip netip.Addr) string {
return joinWithColon(name.EnumString(), ip.String())
}
// newIPv6RangeCIDRBucketKey validates and returns a bucketKey for limits that
// newIPv6RangeCIDRBucketKey returns a bucketKey for limits that
// use the 'enum:ipv6RangeCIDR' bucket key format.
func newIPv6RangeCIDRBucketKey(name Name, ip netip.Addr) (string, error) {
if ip.Is4() {
return "", fmt.Errorf("invalid IPv6 address, %q must be an IPv6 address", ip.String())
}
prefix, err := ip.Prefix(48)
if err != nil {
return "", fmt.Errorf("invalid IPv6 address, can't calculate prefix of %q: %s", ip.String(), err)
}
return joinWithColon(name.EnumString(), prefix.String()), nil
func newIPv6RangeCIDRBucketKey(name Name, prefix netip.Prefix) string {
return joinWithColon(name.EnumString(), prefix.String())
}
// newRegIdBucketKey validates and returns a bucketKey for limits that use the
// newRegIdBucketKey returns a bucketKey for limits that use the
// 'enum:regId' bucket key format.
func newRegIdBucketKey(name Name, regId int64) string {
return joinWithColon(name.EnumString(), strconv.FormatInt(regId, 10))
}
// newDomainOrCIDRBucketKey validates and returns a bucketKey for limits that use
// newDomainOrCIDRBucketKey returns a bucketKey for limits that use
// the 'enum:domainOrCIDR' bucket key formats.
func newDomainOrCIDRBucketKey(name Name, domainOrCIDR string) string {
return joinWithColon(name.EnumString(), domainOrCIDR)
@ -56,7 +49,7 @@ func NewRegIdIdentValueBucketKey(name Name, regId int64, orderIdent string) stri
// newFQDNSetBucketKey validates and returns a bucketKey for limits that use the
// 'enum:fqdnSet' bucket key format.
func newFQDNSetBucketKey(name Name, orderIdents identifier.ACMEIdentifiers) string { //nolint: unparam // Only one named rate limit uses this helper
func newFQDNSetBucketKey(name Name, orderIdents identifier.ACMEIdentifiers) string {
return joinWithColon(name.EnumString(), fmt.Sprintf("%x", core.HashIdentifiers(orderIdents)))
}
@ -80,7 +73,7 @@ func newFQDNSetBucketKey(name Name, orderIdents identifier.ACMEIdentifiers) stri
// it would fail validateTransaction (for instance because cost and burst are zero).
type Transaction struct {
bucketKey string
limit *limit
limit *Limit
cost int64
check bool
spend bool
@ -102,7 +95,7 @@ func validateTransaction(txn Transaction) (Transaction, error) {
if txn.cost < 0 {
return Transaction{}, ErrInvalidCost
}
if txn.limit.burst == 0 {
if txn.limit.Burst == 0 {
// This should never happen. If the limit was loaded from a file,
// Burst was validated then. If this is a zero-valued Transaction
// (that is, an allow-only transaction), then validateTransaction
@ -110,13 +103,13 @@ func validateTransaction(txn Transaction) (Transaction, error) {
// valid.
return Transaction{}, fmt.Errorf("invalid limit, burst must be > 0")
}
if txn.cost > txn.limit.burst {
if txn.cost > txn.limit.Burst {
return Transaction{}, ErrInvalidCostOverLimit
}
return txn, nil
}
func newTransaction(limit *limit, bucketKey string, cost int64) (Transaction, error) {
func newTransaction(limit *Limit, bucketKey string, cost int64) (Transaction, error) {
return validateTransaction(Transaction{
bucketKey: bucketKey,
limit: limit,
@ -126,7 +119,7 @@ func newTransaction(limit *limit, bucketKey string, cost int64) (Transaction, er
})
}
func newCheckOnlyTransaction(limit *limit, bucketKey string, cost int64) (Transaction, error) {
func newCheckOnlyTransaction(limit *Limit, bucketKey string, cost int64) (Transaction, error) {
return validateTransaction(Transaction{
bucketKey: bucketKey,
limit: limit,
@ -135,7 +128,7 @@ func newCheckOnlyTransaction(limit *limit, bucketKey string, cost int64) (Transa
})
}
func newSpendOnlyTransaction(limit *limit, bucketKey string, cost int64) (Transaction, error) {
func newSpendOnlyTransaction(limit *Limit, bucketKey string, cost int64) (Transaction, error) {
return validateTransaction(Transaction{
bucketKey: bucketKey,
limit: limit,
@ -197,10 +190,12 @@ func (builder *TransactionBuilder) registrationsPerIPAddressTransaction(ip netip
// NewRegistrationsPerIPv6Range limit for the /48 IPv6 range which contains the
// provided IPv6 address.
func (builder *TransactionBuilder) registrationsPerIPv6RangeTransaction(ip netip.Addr) (Transaction, error) {
bucketKey, err := newIPv6RangeCIDRBucketKey(NewRegistrationsPerIPv6Range, ip)
prefix, err := coveringIPPrefix(NewRegistrationsPerIPv6Range, ip)
if err != nil {
return Transaction{}, err
return Transaction{}, fmt.Errorf("computing covering prefix for %q: %w", ip, err)
}
bucketKey := newIPv6RangeCIDRBucketKey(NewRegistrationsPerIPv6Range, prefix)
limit, err := builder.getLimit(NewRegistrationsPerIPv6Range, bucketKey)
if err != nil {
if errors.Is(err, errLimitDisabled) {

View File

@ -223,7 +223,7 @@ func TestNewTransactionBuilder(t *testing.T) {
newRegDefault, ok := tb.limitRegistry.defaults[NewRegistrationsPerIPAddress.EnumString()]
test.Assert(t, ok, "NewRegistrationsPerIPAddress was not populated in registry")
test.AssertEquals(t, newRegDefault.burst, expectedBurst)
test.AssertEquals(t, newRegDefault.count, expectedCount)
test.AssertEquals(t, newRegDefault.period, expectedPeriod)
test.AssertEquals(t, newRegDefault.Burst, expectedBurst)
test.AssertEquals(t, newRegDefault.Count, expectedCount)
test.AssertEquals(t, newRegDefault.Period, expectedPeriod)
}

View File

@ -16,57 +16,106 @@ func joinWithColon(args ...string) string {
return strings.Join(args, ":")
}
// coveringIdentifiers transforms a slice of ACMEIdentifiers into strings of
// their "covering" identifiers, for the CertificatesPerDomain limit. It also
// de-duplicates the output. For DNS identifiers, this is eTLD+1's; exact public
// suffix matches are included. For IP address identifiers, this is the address
// (/32) for IPv4, or the /64 prefix for IPv6, in CIDR notation.
// coveringIdentifiers returns the set of "covering" identifiers used to enforce
// the CertificatesPerDomain rate limit. For DNS names, this is the eTLD+1 as
// determined by the Public Suffix List; exact public suffix matches are
// preserved. For IP addresses, the covering prefix is /32 for IPv4 and /64 for
// IPv6. This groups requests by registered domain or address block to match the
// scope of the limit. The result is deduplicated and lowercased. If the
// identifier type is unsupported, an error is returned.
func coveringIdentifiers(idents identifier.ACMEIdentifiers) ([]string, error) {
var covers []string
for _, ident := range idents {
switch ident.Type {
case identifier.TypeDNS:
domain, err := publicsuffix.Domain(ident.Value)
if err != nil {
if err.Error() == fmt.Sprintf("%s is a suffix", ident.Value) {
// If the public suffix is the domain itself, that's fine.
// Include the original name in the result.
covers = append(covers, ident.Value)
continue
} else {
return nil, err
}
}
covers = append(covers, domain)
case identifier.TypeIP:
ip, err := netip.ParseAddr(ident.Value)
if err != nil {
return nil, err
}
prefix, err := coveringPrefix(ip)
if err != nil {
return nil, err
}
covers = append(covers, prefix.String())
cover, err := coveringIdentifier(CertificatesPerDomain, ident)
if err != nil {
return nil, err
}
covers = append(covers, cover)
}
return core.UniqueLowerNames(covers), nil
}
// coveringPrefix transforms a netip.Addr into its "covering" prefix, for the
// CertificatesPerDomain limit. For IPv4, this is the IP address (/32). For
// IPv6, this is the /64 that contains the address.
func coveringPrefix(addr netip.Addr) (netip.Prefix, error) {
var bits int
if addr.Is4() {
bits = 32
} else {
bits = 64
// coveringIdentifier returns the "covering" identifier used to enforce the
// CertificatesPerDomain, CertificatesPerDomainPerAccount, and
// NewRegistrationsPerIPv6Range rate limits. For DNS names, this is the eTLD+1
// as determined by the Public Suffix List; exact public suffix matches are
// preserved. For IP addresses, the covering prefix depends on the limit:
//
// - CertificatesPerDomain and CertificatesPerDomainPerAccount:
// - /32 for IPv4
// - /64 for IPv6
//
// - NewRegistrationsPerIPv6Range:
// - /48 for IPv6 only
//
// This groups requests by registered domain or address block to match the scope
// of each limit. The result is deduplicated and lowercased. If the identifier
// type or limit is unsupported, an error is returned.
func coveringIdentifier(limit Name, ident identifier.ACMEIdentifier) (string, error) {
switch ident.Type {
case identifier.TypeDNS:
domain, err := publicsuffix.Domain(ident.Value)
if err != nil {
if err.Error() == fmt.Sprintf("%s is a suffix", ident.Value) {
// If the public suffix is the domain itself, that's fine.
// Include the original name in the result.
return ident.Value, nil
}
return "", err
}
return domain, nil
case identifier.TypeIP:
ip, err := netip.ParseAddr(ident.Value)
if err != nil {
return "", err
}
prefix, err := coveringIPPrefix(limit, ip)
if err != nil {
return "", err
}
return prefix.String(), nil
}
prefix, err := addr.Prefix(bits)
if err != nil {
// This should be impossible because bits is hardcoded.
return netip.Prefix{}, err
}
return prefix, nil
return "", fmt.Errorf("unsupported identifier type: %s", ident.Type)
}
// coveringIPPrefix returns the "covering" IP prefix used to enforce the
// CertificatesPerDomain, CertificatesPerDomainPerAccount, and
// NewRegistrationsPerIPv6Range rate limits. The prefix length depends on the
// limit and IP version:
//
// - CertificatesPerDomain and CertificatesPerDomainPerAccount:
// - /32 for IPv4
// - /64 for IPv6
//
// - NewRegistrationsPerIPv6Range:
// - /48 for IPv6 only
//
// This groups requests by address block to match the scope of each limit. If
// the limit does not require a covering prefix, an error is returned.
func coveringIPPrefix(limit Name, addr netip.Addr) (netip.Prefix, error) {
switch limit {
case CertificatesPerDomain, CertificatesPerDomainPerAccount:
var bits int
if addr.Is4() {
bits = 32
} else {
bits = 64
}
prefix, err := addr.Prefix(bits)
if err != nil {
return netip.Prefix{}, fmt.Errorf("building covering prefix for %s: %w", addr, err)
}
return prefix, nil
case NewRegistrationsPerIPv6Range:
if !addr.Is6() {
return netip.Prefix{}, fmt.Errorf("limit %s requires an IPv6 address, got %s", limit, addr)
}
prefix, err := addr.Prefix(48)
if err != nil {
return netip.Prefix{}, fmt.Errorf("building covering prefix for %s: %w", addr, err)
}
return prefix, nil
}
return netip.Prefix{}, fmt.Errorf("limit %s does not require a covering prefix", limit)
}

View File

@ -271,7 +271,7 @@ func initTables(dbMap *borp.DbMap) {
dbMap.AddTableWithName(issuedNameModel{}, "issuedNames").SetKeys(true, "ID")
dbMap.AddTableWithName(core.Certificate{}, "certificates").SetKeys(true, "ID")
dbMap.AddTableWithName(certificateStatusModel{}, "certificateStatus").SetKeys(true, "ID")
dbMap.AddTableWithName(core.FQDNSet{}, "fqdnSets").SetKeys(true, "ID")
dbMap.AddTableWithName(fqdnSet{}, "fqdnSets").SetKeys(true, "ID")
tableMap := dbMap.AddTableWithName(orderModel{}, "orders").SetKeys(true, "ID")
if !features.Get().StoreARIReplacesInOrders {
tableMap.ColMap("Replaces").SetTransient(true)

View File

@ -1,10 +0,0 @@
-- +migrate Up
-- SQL in section 'Up' is executed when this migration is applied
ALTER TABLE `registrations` ALTER COLUMN `LockCol` SET DEFAULT 0;
-- +migrate Down
-- SQL section 'Down' is executed when this migration is rolled back
ALTER TABLE `registrations` ALTER COLUMN `LockCol` DROP DEFAULT;

View File

@ -0,0 +1 @@
../../db/boulder_sa/20250110000000_NullRegistrationsLockCol.sql

View File

@ -0,0 +1,9 @@
-- +migrate Up
-- SQL in section 'Up' is executed when this migration is applied
ALTER TABLE `registrations` DROP COLUMN `LockCol`;
-- +migrate Down
-- SQL section 'Down' is executed when this migration is rolled back
ALTER TABLE `registrations` ADD COLUMN `LockCol` BIGINT(20) NOT NULL DEFAULT 0;

View File

@ -0,0 +1,10 @@
-- +migrate Up
-- SQL in section 'Up' is executed when this migration is applied
ALTER TABLE `registrations` ALTER COLUMN `LockCol` SET DEFAULT 0;
-- +migrate Down
-- SQL section 'Down' is executed when this migration is rolled back
ALTER TABLE `registrations` ALTER COLUMN `LockCol` DROP DEFAULT;

View File

@ -12,7 +12,6 @@ import (
"math"
"net/netip"
"net/url"
"slices"
"strconv"
"strings"
"time"
@ -61,69 +60,7 @@ func badJSONError(msg string, jsonData []byte, err error) error {
}
}
const regFields = "id, jwk, jwk_sha256, agreement, createdAt, LockCol, status"
// ClearEmail removes the provided email address from one specified registration. If
// there are multiple email addresses present, it does not modify other ones. If the email
// address is not present, it does not modify the registration and will return a nil error.
func ClearEmail(ctx context.Context, dbMap db.DatabaseMap, regID int64, email string) error {
_, overallError := db.WithTransaction(ctx, dbMap, func(tx db.Executor) (interface{}, error) {
curr, err := selectRegistration(ctx, tx, "id", regID)
if err != nil {
return nil, err
}
currPb, err := registrationModelToPb(curr)
if err != nil {
return nil, err
}
// newContacts will be a copy of all emails in currPb.Contact _except_ the one to be removed
var newContacts []string
for _, contact := range currPb.Contact {
if contact != "mailto:"+email {
newContacts = append(newContacts, contact)
}
}
if slices.Equal(currPb.Contact, newContacts) {
return nil, nil
}
// We don't want to write literal JSON "null" strings into the database if the
// list of contact addresses is empty. Replace any possibly-`nil` slice with
// an empty JSON array. We don't need to check reg.ContactPresent, because
// we're going to write the whole object to the database anyway.
jsonContact := []byte("[]")
if len(newContacts) != 0 {
jsonContact, err = json.Marshal(newContacts)
if err != nil {
return nil, err
}
}
// UPDATE the row with a direct database query, in order to avoid LockCol issues.
result, err := tx.ExecContext(ctx,
"UPDATE registrations SET contact = ? WHERE id = ? LIMIT 1",
jsonContact,
regID,
)
if err != nil {
return nil, err
}
rowsAffected, err := result.RowsAffected()
if err != nil || rowsAffected != 1 {
return nil, berrors.InternalServerError("no registration updated with new contact field")
}
return nil, nil
})
if overallError != nil {
return overallError
}
return nil
}
const regFields = "id, jwk, jwk_sha256, agreement, createdAt, status"
// selectRegistration selects all fields of one registration model
func selectRegistration(ctx context.Context, s db.OneSelector, whereCol string, args ...interface{}) (*regModel, error) {
@ -275,8 +212,7 @@ type regModel struct {
KeySHA256 string `db:"jwk_sha256"`
Agreement string `db:"agreement"`
CreatedAt time.Time `db:"createdAt"`
LockCol int64
Status string `db:"status"`
Status string `db:"status"`
}
func registrationPbToModel(reg *corepb.Registration) (*regModel, error) {
@ -480,15 +416,17 @@ func modelToOrder(om *orderModel) (*corepb.Order, error) {
}
var challTypeToUint = map[string]uint8{
"http-01": 0,
"dns-01": 1,
"tls-alpn-01": 2,
"http-01": 0,
"dns-01": 1,
"tls-alpn-01": 2,
"dns-account-01": 3,
}
var uintToChallType = map[uint8]string{
0: "http-01",
1: "dns-01",
2: "tls-alpn-01",
3: "dns-account-01",
}
var identifierTypeToUint = map[string]uint8{
@ -963,6 +901,16 @@ type crlEntryModel struct {
RevokedDate time.Time `db:"revokedDate"`
}
// fqdnSet contains the SHA256 hash of the lowercased, comma joined dNSNames
// contained in a certificate.
type fqdnSet struct {
ID int64
SetHash []byte
Serial string
Issued time.Time
Expires time.Time
}
// orderFQDNSet contains the SHA256 hash of the lowercased, comma joined names
// from a new-order request, along with the corresponding orderID, the
// registration ID, and the order expiry. This is used to find
@ -976,7 +924,7 @@ type orderFQDNSet struct {
}
func addFQDNSet(ctx context.Context, db db.Inserter, idents identifier.ACMEIdentifiers, serial string, issued time.Time, expires time.Time) error {
return db.Insert(ctx, &core.FQDNSet{
return db.Insert(ctx, &fqdnSet{
SetHash: core.HashIdentifiers(idents),
Serial: serial,
Issued: issued,

File diff suppressed because it is too large Load Diff

View File

@ -39,7 +39,7 @@ service StorageAuthorityReadOnly {
rpc CheckIdentifiersPaused (PauseRequest) returns (Identifiers) {}
rpc GetPausedIdentifiers (RegistrationID) returns (Identifiers) {}
rpc GetRateLimitOverride(GetRateLimitOverrideRequest) returns (RateLimitOverrideResponse) {}
rpc GetEnabledRateLimitOverrides(google.protobuf.Empty) returns (stream RateLimitOverride) {}
rpc GetEnabledRateLimitOverrides(google.protobuf.Empty) returns (stream RateLimitOverrideResponse) {}
}
// StorageAuthority provides full read/write access to the database.
@ -74,7 +74,7 @@ service StorageAuthority {
rpc CheckIdentifiersPaused (PauseRequest) returns (Identifiers) {}
rpc GetPausedIdentifiers (RegistrationID) returns (Identifiers) {}
rpc GetRateLimitOverride(GetRateLimitOverrideRequest) returns (RateLimitOverrideResponse) {}
rpc GetEnabledRateLimitOverrides(google.protobuf.Empty) returns (stream RateLimitOverride) {}
rpc GetEnabledRateLimitOverrides(google.protobuf.Empty) returns (stream RateLimitOverrideResponse) {}
// Adders
rpc AddBlockedKey(AddBlockedKeyRequest) returns (google.protobuf.Empty) {}
@ -91,7 +91,6 @@ service StorageAuthority {
rpc RevokeCertificate(RevokeCertificateRequest) returns (google.protobuf.Empty) {}
rpc SetOrderError(SetOrderErrorRequest) returns (google.protobuf.Empty) {}
rpc SetOrderProcessing(OrderRequest) returns (google.protobuf.Empty) {}
rpc UpdateRegistrationContact(UpdateRegistrationContactRequest) returns (core.Registration) {}
rpc UpdateRegistrationKey(UpdateRegistrationKeyRequest) returns (core.Registration) {}
rpc UpdateRevokedCertificate(RevokeCertificateRequest) returns (google.protobuf.Empty) {}
rpc LeaseCRLShard(LeaseCRLShardRequest) returns (LeaseCRLShardResponse) {}
@ -436,11 +435,6 @@ message PauseIdentifiersResponse {
int64 repaused = 2;
}
message UpdateRegistrationContactRequest {
int64 registrationID = 1;
repeated string contacts = 2;
}
message UpdateRegistrationKeyRequest {
int64 registrationID = 1;
bytes jwk = 2;

View File

@ -89,7 +89,7 @@ type StorageAuthorityReadOnlyClient interface {
CheckIdentifiersPaused(ctx context.Context, in *PauseRequest, opts ...grpc.CallOption) (*Identifiers, error)
GetPausedIdentifiers(ctx context.Context, in *RegistrationID, opts ...grpc.CallOption) (*Identifiers, error)
GetRateLimitOverride(ctx context.Context, in *GetRateLimitOverrideRequest, opts ...grpc.CallOption) (*RateLimitOverrideResponse, error)
GetEnabledRateLimitOverrides(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[RateLimitOverride], error)
GetEnabledRateLimitOverrides(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[RateLimitOverrideResponse], error)
}
type storageAuthorityReadOnlyClient struct {
@ -435,13 +435,13 @@ func (c *storageAuthorityReadOnlyClient) GetRateLimitOverride(ctx context.Contex
return out, nil
}
func (c *storageAuthorityReadOnlyClient) GetEnabledRateLimitOverrides(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[RateLimitOverride], error) {
func (c *storageAuthorityReadOnlyClient) GetEnabledRateLimitOverrides(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[RateLimitOverrideResponse], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &StorageAuthorityReadOnly_ServiceDesc.Streams[5], StorageAuthorityReadOnly_GetEnabledRateLimitOverrides_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
x := &grpc.GenericClientStream[emptypb.Empty, RateLimitOverride]{ClientStream: stream}
x := &grpc.GenericClientStream[emptypb.Empty, RateLimitOverrideResponse]{ClientStream: stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
@ -452,7 +452,7 @@ func (c *storageAuthorityReadOnlyClient) GetEnabledRateLimitOverrides(ctx contex
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type StorageAuthorityReadOnly_GetEnabledRateLimitOverridesClient = grpc.ServerStreamingClient[RateLimitOverride]
type StorageAuthorityReadOnly_GetEnabledRateLimitOverridesClient = grpc.ServerStreamingClient[RateLimitOverrideResponse]
// StorageAuthorityReadOnlyServer is the server API for StorageAuthorityReadOnly service.
// All implementations must embed UnimplementedStorageAuthorityReadOnlyServer
@ -489,7 +489,7 @@ type StorageAuthorityReadOnlyServer interface {
CheckIdentifiersPaused(context.Context, *PauseRequest) (*Identifiers, error)
GetPausedIdentifiers(context.Context, *RegistrationID) (*Identifiers, error)
GetRateLimitOverride(context.Context, *GetRateLimitOverrideRequest) (*RateLimitOverrideResponse, error)
GetEnabledRateLimitOverrides(*emptypb.Empty, grpc.ServerStreamingServer[RateLimitOverride]) error
GetEnabledRateLimitOverrides(*emptypb.Empty, grpc.ServerStreamingServer[RateLimitOverrideResponse]) error
mustEmbedUnimplementedStorageAuthorityReadOnlyServer()
}
@ -587,7 +587,7 @@ func (UnimplementedStorageAuthorityReadOnlyServer) GetPausedIdentifiers(context.
func (UnimplementedStorageAuthorityReadOnlyServer) GetRateLimitOverride(context.Context, *GetRateLimitOverrideRequest) (*RateLimitOverrideResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetRateLimitOverride not implemented")
}
func (UnimplementedStorageAuthorityReadOnlyServer) GetEnabledRateLimitOverrides(*emptypb.Empty, grpc.ServerStreamingServer[RateLimitOverride]) error {
func (UnimplementedStorageAuthorityReadOnlyServer) GetEnabledRateLimitOverrides(*emptypb.Empty, grpc.ServerStreamingServer[RateLimitOverrideResponse]) error {
return status.Errorf(codes.Unimplemented, "method GetEnabledRateLimitOverrides not implemented")
}
func (UnimplementedStorageAuthorityReadOnlyServer) mustEmbedUnimplementedStorageAuthorityReadOnlyServer() {
@ -1104,11 +1104,11 @@ func _StorageAuthorityReadOnly_GetEnabledRateLimitOverrides_Handler(srv interfac
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(StorageAuthorityReadOnlyServer).GetEnabledRateLimitOverrides(m, &grpc.GenericServerStream[emptypb.Empty, RateLimitOverride]{ServerStream: stream})
return srv.(StorageAuthorityReadOnlyServer).GetEnabledRateLimitOverrides(m, &grpc.GenericServerStream[emptypb.Empty, RateLimitOverrideResponse]{ServerStream: stream})
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type StorageAuthorityReadOnly_GetEnabledRateLimitOverridesServer = grpc.ServerStreamingServer[RateLimitOverride]
type StorageAuthorityReadOnly_GetEnabledRateLimitOverridesServer = grpc.ServerStreamingServer[RateLimitOverrideResponse]
// StorageAuthorityReadOnly_ServiceDesc is the grpc.ServiceDesc for StorageAuthorityReadOnly service.
// It's only intended for direct use with grpc.RegisterService,
@ -1294,7 +1294,6 @@ const (
StorageAuthority_RevokeCertificate_FullMethodName = "/sa.StorageAuthority/RevokeCertificate"
StorageAuthority_SetOrderError_FullMethodName = "/sa.StorageAuthority/SetOrderError"
StorageAuthority_SetOrderProcessing_FullMethodName = "/sa.StorageAuthority/SetOrderProcessing"
StorageAuthority_UpdateRegistrationContact_FullMethodName = "/sa.StorageAuthority/UpdateRegistrationContact"
StorageAuthority_UpdateRegistrationKey_FullMethodName = "/sa.StorageAuthority/UpdateRegistrationKey"
StorageAuthority_UpdateRevokedCertificate_FullMethodName = "/sa.StorageAuthority/UpdateRevokedCertificate"
StorageAuthority_LeaseCRLShard_FullMethodName = "/sa.StorageAuthority/LeaseCRLShard"
@ -1342,7 +1341,7 @@ type StorageAuthorityClient interface {
CheckIdentifiersPaused(ctx context.Context, in *PauseRequest, opts ...grpc.CallOption) (*Identifiers, error)
GetPausedIdentifiers(ctx context.Context, in *RegistrationID, opts ...grpc.CallOption) (*Identifiers, error)
GetRateLimitOverride(ctx context.Context, in *GetRateLimitOverrideRequest, opts ...grpc.CallOption) (*RateLimitOverrideResponse, error)
GetEnabledRateLimitOverrides(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[RateLimitOverride], error)
GetEnabledRateLimitOverrides(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[RateLimitOverrideResponse], error)
// Adders
AddBlockedKey(ctx context.Context, in *AddBlockedKeyRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
AddCertificate(ctx context.Context, in *AddCertificateRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
@ -1358,7 +1357,6 @@ type StorageAuthorityClient interface {
RevokeCertificate(ctx context.Context, in *RevokeCertificateRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
SetOrderError(ctx context.Context, in *SetOrderErrorRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
SetOrderProcessing(ctx context.Context, in *OrderRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
UpdateRegistrationContact(ctx context.Context, in *UpdateRegistrationContactRequest, opts ...grpc.CallOption) (*proto.Registration, error)
UpdateRegistrationKey(ctx context.Context, in *UpdateRegistrationKeyRequest, opts ...grpc.CallOption) (*proto.Registration, error)
UpdateRevokedCertificate(ctx context.Context, in *RevokeCertificateRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
LeaseCRLShard(ctx context.Context, in *LeaseCRLShardRequest, opts ...grpc.CallOption) (*LeaseCRLShardResponse, error)
@ -1713,13 +1711,13 @@ func (c *storageAuthorityClient) GetRateLimitOverride(ctx context.Context, in *G
return out, nil
}
func (c *storageAuthorityClient) GetEnabledRateLimitOverrides(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[RateLimitOverride], error) {
func (c *storageAuthorityClient) GetEnabledRateLimitOverrides(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[RateLimitOverrideResponse], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &StorageAuthority_ServiceDesc.Streams[5], StorageAuthority_GetEnabledRateLimitOverrides_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
x := &grpc.GenericClientStream[emptypb.Empty, RateLimitOverride]{ClientStream: stream}
x := &grpc.GenericClientStream[emptypb.Empty, RateLimitOverrideResponse]{ClientStream: stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
@ -1730,7 +1728,7 @@ func (c *storageAuthorityClient) GetEnabledRateLimitOverrides(ctx context.Contex
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type StorageAuthority_GetEnabledRateLimitOverridesClient = grpc.ServerStreamingClient[RateLimitOverride]
type StorageAuthority_GetEnabledRateLimitOverridesClient = grpc.ServerStreamingClient[RateLimitOverrideResponse]
func (c *storageAuthorityClient) AddBlockedKey(ctx context.Context, in *AddBlockedKeyRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
@ -1872,16 +1870,6 @@ func (c *storageAuthorityClient) SetOrderProcessing(ctx context.Context, in *Ord
return out, nil
}
func (c *storageAuthorityClient) UpdateRegistrationContact(ctx context.Context, in *UpdateRegistrationContactRequest, opts ...grpc.CallOption) (*proto.Registration, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(proto.Registration)
err := c.cc.Invoke(ctx, StorageAuthority_UpdateRegistrationContact_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *storageAuthorityClient) UpdateRegistrationKey(ctx context.Context, in *UpdateRegistrationKeyRequest, opts ...grpc.CallOption) (*proto.Registration, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(proto.Registration)
@ -2008,7 +1996,7 @@ type StorageAuthorityServer interface {
CheckIdentifiersPaused(context.Context, *PauseRequest) (*Identifiers, error)
GetPausedIdentifiers(context.Context, *RegistrationID) (*Identifiers, error)
GetRateLimitOverride(context.Context, *GetRateLimitOverrideRequest) (*RateLimitOverrideResponse, error)
GetEnabledRateLimitOverrides(*emptypb.Empty, grpc.ServerStreamingServer[RateLimitOverride]) error
GetEnabledRateLimitOverrides(*emptypb.Empty, grpc.ServerStreamingServer[RateLimitOverrideResponse]) error
// Adders
AddBlockedKey(context.Context, *AddBlockedKeyRequest) (*emptypb.Empty, error)
AddCertificate(context.Context, *AddCertificateRequest) (*emptypb.Empty, error)
@ -2024,7 +2012,6 @@ type StorageAuthorityServer interface {
RevokeCertificate(context.Context, *RevokeCertificateRequest) (*emptypb.Empty, error)
SetOrderError(context.Context, *SetOrderErrorRequest) (*emptypb.Empty, error)
SetOrderProcessing(context.Context, *OrderRequest) (*emptypb.Empty, error)
UpdateRegistrationContact(context.Context, *UpdateRegistrationContactRequest) (*proto.Registration, error)
UpdateRegistrationKey(context.Context, *UpdateRegistrationKeyRequest) (*proto.Registration, error)
UpdateRevokedCertificate(context.Context, *RevokeCertificateRequest) (*emptypb.Empty, error)
LeaseCRLShard(context.Context, *LeaseCRLShardRequest) (*LeaseCRLShardResponse, error)
@ -2131,7 +2118,7 @@ func (UnimplementedStorageAuthorityServer) GetPausedIdentifiers(context.Context,
func (UnimplementedStorageAuthorityServer) GetRateLimitOverride(context.Context, *GetRateLimitOverrideRequest) (*RateLimitOverrideResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetRateLimitOverride not implemented")
}
func (UnimplementedStorageAuthorityServer) GetEnabledRateLimitOverrides(*emptypb.Empty, grpc.ServerStreamingServer[RateLimitOverride]) error {
func (UnimplementedStorageAuthorityServer) GetEnabledRateLimitOverrides(*emptypb.Empty, grpc.ServerStreamingServer[RateLimitOverrideResponse]) error {
return status.Errorf(codes.Unimplemented, "method GetEnabledRateLimitOverrides not implemented")
}
func (UnimplementedStorageAuthorityServer) AddBlockedKey(context.Context, *AddBlockedKeyRequest) (*emptypb.Empty, error) {
@ -2176,9 +2163,6 @@ func (UnimplementedStorageAuthorityServer) SetOrderError(context.Context, *SetOr
func (UnimplementedStorageAuthorityServer) SetOrderProcessing(context.Context, *OrderRequest) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetOrderProcessing not implemented")
}
func (UnimplementedStorageAuthorityServer) UpdateRegistrationContact(context.Context, *UpdateRegistrationContactRequest) (*proto.Registration, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateRegistrationContact not implemented")
}
func (UnimplementedStorageAuthorityServer) UpdateRegistrationKey(context.Context, *UpdateRegistrationKeyRequest) (*proto.Registration, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateRegistrationKey not implemented")
}
@ -2719,11 +2703,11 @@ func _StorageAuthority_GetEnabledRateLimitOverrides_Handler(srv interface{}, str
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(StorageAuthorityServer).GetEnabledRateLimitOverrides(m, &grpc.GenericServerStream[emptypb.Empty, RateLimitOverride]{ServerStream: stream})
return srv.(StorageAuthorityServer).GetEnabledRateLimitOverrides(m, &grpc.GenericServerStream[emptypb.Empty, RateLimitOverrideResponse]{ServerStream: stream})
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type StorageAuthority_GetEnabledRateLimitOverridesServer = grpc.ServerStreamingServer[RateLimitOverride]
type StorageAuthority_GetEnabledRateLimitOverridesServer = grpc.ServerStreamingServer[RateLimitOverrideResponse]
func _StorageAuthority_AddBlockedKey_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(AddBlockedKeyRequest)
@ -2977,24 +2961,6 @@ func _StorageAuthority_SetOrderProcessing_Handler(srv interface{}, ctx context.C
return interceptor(ctx, in, info, handler)
}
func _StorageAuthority_UpdateRegistrationContact_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(UpdateRegistrationContactRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(StorageAuthorityServer).UpdateRegistrationContact(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: StorageAuthority_UpdateRegistrationContact_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(StorageAuthorityServer).UpdateRegistrationContact(ctx, req.(*UpdateRegistrationContactRequest))
}
return interceptor(ctx, in, info, handler)
}
func _StorageAuthority_UpdateRegistrationKey_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(UpdateRegistrationKeyRequest)
if err := dec(in); err != nil {
@ -3316,10 +3282,6 @@ var StorageAuthority_ServiceDesc = grpc.ServiceDesc{
MethodName: "SetOrderProcessing",
Handler: _StorageAuthority_SetOrderProcessing_Handler,
},
{
MethodName: "UpdateRegistrationContact",
Handler: _StorageAuthority_UpdateRegistrationContact_Handler,
},
{
MethodName: "UpdateRegistrationKey",
Handler: _StorageAuthority_UpdateRegistrationKey_Handler,

View File

@ -125,14 +125,6 @@ func (ssa *SQLStorageAuthority) NewRegistration(ctx context.Context, req *corepb
return registrationModelToPb(reg)
}
// UpdateRegistrationContact makes no changes, and simply returns the account
// as it exists in the database.
//
// Deprecated: See https://github.com/letsencrypt/boulder/issues/8199 for removal.
func (ssa *SQLStorageAuthority) UpdateRegistrationContact(ctx context.Context, req *sapb.UpdateRegistrationContactRequest) (*corepb.Registration, error) {
return ssa.GetRegistration(ctx, &sapb.RegistrationID{Id: req.RegistrationID})
}
// UpdateRegistrationKey stores an updated key in a Registration.
func (ssa *SQLStorageAuthority) UpdateRegistrationKey(ctx context.Context, req *sapb.UpdateRegistrationKeyRequest) (*corepb.Registration, error) {
if core.IsAnyNilOrZero(req.RegistrationID, req.Jwk) {
@ -407,7 +399,7 @@ func (ssa *SQLStorageAuthority) AddCertificate(ctx context.Context, req *sapb.Ad
return &emptypb.Empty{}, nil
}
// DeactivateRegistration deactivates a currently valid registration and removes its contact field
// DeactivateRegistration deactivates a currently valid registration
func (ssa *SQLStorageAuthority) DeactivateRegistration(ctx context.Context, req *sapb.RegistrationID) (*corepb.Registration, error) {
if req == nil || req.Id == 0 {
return nil, errIncompleteRequest

View File

@ -126,7 +126,6 @@ func initSA(t testing.TB) (*SQLStorageAuthority, clock.FakeClock, func()) {
func createWorkingRegistration(t testing.TB, sa *SQLStorageAuthority) *corepb.Registration {
reg, err := sa.NewRegistration(context.Background(), &corepb.Registration{
Key: []byte(theKey),
Contact: []string{"mailto:foo@example.com"},
CreatedAt: mustTimestamp("2003-05-10 00:00"),
Status: string(core.StatusValid),
})
@ -190,14 +189,12 @@ func TestAddRegistration(t *testing.T) {
jwkJSON, _ := goodTestJWK().MarshalJSON()
reg, err := sa.NewRegistration(ctx, &corepb.Registration{
Key: jwkJSON,
Contact: []string{"mailto:foo@example.com"},
Key: jwkJSON,
})
if err != nil {
t.Fatalf("Couldn't create new registration: %s", err)
}
test.Assert(t, reg.Id != 0, "ID shouldn't be 0")
test.AssertEquals(t, len(reg.Contact), 0)
// Confirm that the registration can be retrieved by ID.
dbReg, err := sa.GetRegistration(ctx, &sapb.RegistrationID{Id: reg.Id})
@ -207,7 +204,6 @@ func TestAddRegistration(t *testing.T) {
test.AssertEquals(t, dbReg.Id, reg.Id)
test.AssertByteEquals(t, dbReg.Key, jwkJSON)
test.AssertDeepEquals(t, dbReg.CreatedAt.AsTime(), createdAt)
test.AssertEquals(t, len(dbReg.Contact), 0)
_, err = sa.GetRegistration(ctx, &sapb.RegistrationID{Id: 0})
test.AssertError(t, err, "Registration object for ID 0 was returned")
@ -254,8 +250,7 @@ func TestSelectRegistration(t *testing.T) {
test.AssertNotError(t, err, "couldn't parse jwk.Key")
reg, err := sa.NewRegistration(ctx, &corepb.Registration{
Key: jwkJSON,
Contact: []string{"mailto:foo@example.com"},
Key: jwkJSON,
})
test.AssertNotError(t, err, fmt.Sprintf("couldn't create new registration: %s", err))
test.Assert(t, reg.Id != 0, "ID shouldn't be 0")
@ -884,7 +879,6 @@ func TestDeactivateAccount(t *testing.T) {
test.AssertNotError(t, err, "DeactivateRegistration failed")
test.AssertEquals(t, got.Id, reg.Id)
test.AssertEquals(t, core.AcmeStatus(got.Status), core.StatusDeactivated)
test.AssertEquals(t, len(got.Contact), 0)
// Double-check that the DeactivateRegistration method returned the right
// thing, by fetching the same account ourselves.
@ -892,7 +886,6 @@ func TestDeactivateAccount(t *testing.T) {
test.AssertNotError(t, err, "GetRegistration failed")
test.AssertEquals(t, got.Id, reg.Id)
test.AssertEquals(t, core.AcmeStatus(got.Status), core.StatusDeactivated)
test.AssertEquals(t, len(got.Contact), 0)
// Attempting to deactivate it a second time should fail, since it is already
// deactivated.
@ -2639,6 +2632,36 @@ func TestGetValidAuthorizations2(t *testing.T) {
aaa = am.ID
}
var dac int64
{
tokenStr := core.NewToken()
token, err := base64.RawURLEncoding.DecodeString(tokenStr)
test.AssertNotError(t, err, "computing test authorization challenge token")
profile := "test"
attempted := challTypeToUint[string(core.ChallengeTypeDNSAccount01)]
attemptedAt := fc.Now()
vr, _ := json.Marshal([]core.ValidationRecord{})
am := authzModel{
IdentifierType: identifierTypeToUint[string(identifier.TypeDNS)],
IdentifierValue: "aaa",
RegistrationID: 3,
CertificateProfileName: &profile,
Status: statusToUint[core.StatusValid],
Expires: fc.Now().Add(24 * time.Hour),
Challenges: 1 << challTypeToUint[string(core.ChallengeTypeDNSAccount01)],
Attempted: &attempted,
AttemptedAt: &attemptedAt,
Token: token,
ValidationError: nil,
ValidationRecord: vr,
}
err = sa.dbMap.Insert(context.Background(), &am)
test.AssertNotError(t, err, "failed to insert valid authz with dns-account-01")
dac = am.ID
}
for _, tc := range []struct {
name string
regID int64
@ -2655,6 +2678,14 @@ func TestGetValidAuthorizations2(t *testing.T) {
validUntil: fc.Now().Add(time.Hour),
wantIDs: []int64{aaa},
},
{
name: "happy path, dns-account-01 challenge",
regID: 3,
identifiers: []*corepb.Identifier{identifier.NewDNS("aaa").ToProto()},
profile: "test",
validUntil: fc.Now().Add(time.Hour),
wantIDs: []int64{dac},
},
{
name: "different identifier type",
regID: 1,
@ -4492,74 +4523,6 @@ func newAcctKey(t *testing.T) []byte {
return acctKey
}
func TestUpdateRegistrationContact(t *testing.T) {
// TODO(#8199): Delete this.
sa, _, cleanUp := initSA(t)
defer cleanUp()
noContact, _ := json.Marshal("")
exampleContact, _ := json.Marshal("test@example.com")
twoExampleContacts, _ := json.Marshal([]string{"test1@example.com", "test2@example.com"})
_, err := sa.UpdateRegistrationContact(ctx, &sapb.UpdateRegistrationContactRequest{})
test.AssertError(t, err, "should not have been able to update registration contact without a registration ID")
test.AssertContains(t, err.Error(), "incomplete gRPC request message")
tests := []struct {
name string
oldContactsJSON []string
newContacts []string
}{
{
name: "update a valid registration from no contacts to one email address",
oldContactsJSON: []string{string(noContact)},
newContacts: []string{"mailto:test@example.com"},
},
{
name: "update a valid registration from no contacts to two email addresses",
oldContactsJSON: []string{string(noContact)},
newContacts: []string{"mailto:test1@example.com", "mailto:test2@example.com"},
},
{
name: "update a valid registration from one email address to no contacts",
oldContactsJSON: []string{string(exampleContact)},
newContacts: []string{},
},
{
name: "update a valid registration from one email address to two email addresses",
oldContactsJSON: []string{string(exampleContact)},
newContacts: []string{"mailto:test1@example.com", "mailto:test2@example.com"},
},
{
name: "update a valid registration from two email addresses to no contacts",
oldContactsJSON: []string{string(twoExampleContacts)},
newContacts: []string{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
reg, err := sa.NewRegistration(ctx, &corepb.Registration{
Contact: tt.oldContactsJSON,
Key: newAcctKey(t),
})
test.AssertNotError(t, err, "creating new registration")
updatedReg, err := sa.UpdateRegistrationContact(ctx, &sapb.UpdateRegistrationContactRequest{
RegistrationID: reg.Id,
Contacts: tt.newContacts,
})
test.AssertNotError(t, err, "unexpected error for UpdateRegistrationContact()")
test.AssertEquals(t, updatedReg.Id, reg.Id)
test.AssertEquals(t, len(updatedReg.Contact), 0)
refetchedReg, err := sa.GetRegistration(ctx, &sapb.RegistrationID{Id: reg.Id})
test.AssertNotError(t, err, "retrieving registration")
test.AssertEquals(t, refetchedReg.Id, reg.Id)
test.AssertEquals(t, len(refetchedReg.Contact), 0)
})
}
}
func TestUpdateRegistrationKey(t *testing.T) {
sa, _, cleanUp := initSA(t)
defer cleanUp()
@ -4624,7 +4587,7 @@ func TestUpdateRegistrationKey(t *testing.T) {
type mockRLOStream struct {
grpc.ServerStream
sent []*sapb.RateLimitOverride
sent []*sapb.RateLimitOverrideResponse
ctx context.Context
}
@ -4633,7 +4596,7 @@ func newMockRLOStream() *mockRLOStream {
}
func (m *mockRLOStream) Context() context.Context { return m.ctx }
func (m *mockRLOStream) RecvMsg(any) error { return io.EOF }
func (m *mockRLOStream) Send(ov *sapb.RateLimitOverride) error {
func (m *mockRLOStream) Send(ov *sapb.RateLimitOverrideResponse) error {
m.sent = append(m.sent, ov)
return nil
}
@ -4767,5 +4730,5 @@ func TestGetEnabledRateLimitOverrides(t *testing.T) {
err = sa.GetEnabledRateLimitOverrides(&emptypb.Empty{}, stream)
test.AssertNotError(t, err, "expected streaming enabled overrides to succeed, got error")
test.AssertEquals(t, len(stream.sent), 1)
test.AssertEquals(t, stream.sent[0].BucketKey, "on")
test.AssertEquals(t, stream.sent[0].Override.BucketKey, "on")
}

View File

@ -1304,6 +1304,10 @@ func (ssa *SQLStorageAuthorityRO) GetEnabledRateLimitOverrides(_ *emptypb.Empty,
}
return rows.ForEach(func(m *overrideModel) error {
return stream.Send(newPBFromOverrideModel(m))
return stream.Send(&sapb.RateLimitOverrideResponse{
Override: newPBFromOverrideModel(m),
Enabled: m.Enabled,
UpdatedAt: timestamppb.New(m.UpdatedAt),
})
})
}

View File

@ -5,10 +5,11 @@ import (
"testing"
"time"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/letsencrypt/boulder/core"
corepb "github.com/letsencrypt/boulder/core/proto"
sapb "github.com/letsencrypt/boulder/sa/proto"
"google.golang.org/protobuf/types/known/timestamppb"
)
// CreateWorkingRegistration inserts a new, correct Registration into
@ -21,7 +22,6 @@ func CreateWorkingRegistration(t *testing.T, sa sapb.StorageAuthorityClient) *co
"n": "n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT-O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqVwGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuCLqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5gHdrNP5zw",
"e": "AQAB"
}`),
Contact: []string{"mailto:foo@example.com"},
CreatedAt: timestamppb.New(time.Date(2003, 5, 10, 0, 0, 0, 0, time.UTC)),
Status: string(core.StatusValid),
})

View File

@ -22,7 +22,8 @@
"maximumRevocations": 15,
"findCertificatesBatchSize": 10,
"interval": "50ms",
"backoffIntervalMax": "2s"
"backoffIntervalMax": "2s",
"maxExpectedReplicationLag": "100ms"
},
"syslog": {
"stdoutlevel": 4,

View File

@ -5,7 +5,7 @@
"certFile": "test/certs/ipki/ca.boulder/cert.pem",
"keyFile": "test/certs/ipki/ca.boulder/key.pem"
},
"hostnamePolicyFile": "test/hostname-policy.yaml",
"hostnamePolicyFile": "test/ident-policy.yaml",
"grpcCA": {
"maxConnectionAge": "30s",
"services": {
@ -54,28 +54,20 @@
"issuance": {
"certProfiles": {
"legacy": {
"omitCommonName": false,
"omitKeyEncipherment": false,
"omitClientAuth": false,
"omitSKID": false,
"includeCRLDistributionPoints": true,
"maxValidityPeriod": "7776000s",
"maxValidityBackdate": "1h5m",
"lintConfig": "test/config-next/zlint.toml",
"ignoredLints": [
"w_subject_common_name_included",
"e_dnsname_not_valid_tld",
"w_ext_subject_key_identifier_not_recommended_subscriber"
]
},
"modern": {
"omitCommonName": true,
"omitKeyEncipherment": true,
"omitClientAuth": true,
"omitSKID": true,
"includeCRLDistributionPoints": true,
"maxValidityPeriod": "583200s",
"maxValidityBackdate": "1h5m",
"lintConfig": "test/config-next/zlint.toml",
"ignoredLints": [
"w_ext_subject_key_identifier_missing_sub_cert"
]
},
"shortlived": {
"omitCommonName": true,
"omitKeyEncipherment": true,
@ -86,7 +78,22 @@
"maxValidityBackdate": "1h5m",
"lintConfig": "test/config-next/zlint.toml",
"ignoredLints": [
"w_ext_subject_key_identifier_missing_sub_cert"
"w_ext_subject_key_identifier_missing_sub_cert",
"e_dnsname_not_valid_tld"
]
},
"modern": {
"omitCommonName": true,
"omitKeyEncipherment": true,
"omitClientAuth": true,
"omitSKID": true,
"includeCRLDistributionPoints": true,
"maxValidityPeriod": "583200s",
"maxValidityBackdate": "1h5m",
"lintConfig": "test/config-next/zlint.toml",
"ignoredLints": [
"w_ext_subject_key_identifier_missing_sub_cert",
"e_dnsname_not_valid_tld"
]
}
},

View File

@ -4,18 +4,20 @@
"dbConnectFile": "test/secrets/cert_checker_dburl",
"maxOpenConns": 10
},
"hostnamePolicyFile": "test/hostname-policy.yaml",
"hostnamePolicyFile": "test/ident-policy.yaml",
"workers": 16,
"unexpiredOnly": true,
"badResultsOnly": true,
"checkPeriod": "72h",
"acceptableValidityDurations": [
"7776000s"
"7776000s",
"160h"
],
"lintConfig": "test/config-next/zlint.toml",
"ignoredLints": [
"w_subject_common_name_included",
"w_ext_subject_key_identifier_missing_sub_cert",
"w_subject_common_name_included",
"e_dnsname_not_valid_tld",
"w_ext_subject_key_identifier_not_recommended_subscriber"
],
"ctLogListFile": "test/ct-test-srv/log_list.json",

View File

@ -25,7 +25,7 @@
"Overrides": "test/config-next/wfe2-ratelimit-overrides.yml"
},
"maxContactsPerRegistration": 3,
"hostnamePolicyFile": "test/hostname-policy.yaml",
"hostnamePolicyFile": "test/ident-policy.yaml",
"goodkey": {},
"finalizeTimeout": "30s",
"issuerCerts": [

View File

@ -20,20 +20,11 @@
"noWaitForReady": true,
"timeout": "15s"
},
"mailer": {
"server": "localhost",
"port": "9380",
"username": "cert-manager@example.com",
"from": "bad key revoker <bad-key-revoker@test.org>",
"passwordFile": "test/secrets/smtp_password",
"SMTPTrustedRootFile": "test/certs/ipki/minica.pem",
"emailSubject": "Certificates you've issued have been revoked due to key compromise",
"emailTemplate": "test/example-bad-key-revoker-template"
},
"maximumRevocations": 15,
"findCertificatesBatchSize": 10,
"interval": "50ms",
"backoffIntervalMax": "2s"
"backoffIntervalMax": "2s",
"maxExpectedReplicationLag": "100ms"
},
"syslog": {
"stdoutlevel": 4,

View File

@ -5,7 +5,7 @@
"certFile": "test/certs/ipki/ca.boulder/cert.pem",
"keyFile": "test/certs/ipki/ca.boulder/key.pem"
},
"hostnamePolicyFile": "test/hostname-policy.yaml",
"hostnamePolicyFile": "test/ident-policy.yaml",
"grpcCA": {
"maxConnectionAge": "30s",
"address": ":9093",
@ -55,7 +55,11 @@
"issuance": {
"certProfiles": {
"legacy": {
"allowMustStaple": true,
"allowMustStaple": false,
"omitCommonName": false,
"omitKeyEncipherment": false,
"omitClientAuth": false,
"omitSKID": false,
"omitOCSP": true,
"includeCRLDistributionPoints": true,
"maxValidityPeriod": "7776000s",
@ -63,26 +67,12 @@
"lintConfig": "test/config-next/zlint.toml",
"ignoredLints": [
"w_subject_common_name_included",
"e_dnsname_not_valid_tld",
"w_ext_subject_key_identifier_not_recommended_subscriber"
]
},
"modern": {
"allowMustStaple": true,
"omitCommonName": true,
"omitKeyEncipherment": true,
"omitClientAuth": true,
"omitSKID": true,
"omitOCSP": true,
"includeCRLDistributionPoints": true,
"maxValidityPeriod": "583200s",
"maxValidityBackdate": "1h5m",
"lintConfig": "test/config-next/zlint.toml",
"ignoredLints": [
"w_ext_subject_key_identifier_missing_sub_cert"
]
},
"shortlived": {
"allowMustStaple": true,
"allowMustStaple": false,
"omitCommonName": true,
"omitKeyEncipherment": true,
"omitClientAuth": true,
@ -93,7 +83,24 @@
"maxValidityBackdate": "1h5m",
"lintConfig": "test/config-next/zlint.toml",
"ignoredLints": [
"w_ext_subject_key_identifier_missing_sub_cert"
"w_ext_subject_key_identifier_missing_sub_cert",
"e_dnsname_not_valid_tld"
]
},
"modern": {
"allowMustStaple": false,
"omitCommonName": true,
"omitKeyEncipherment": true,
"omitClientAuth": true,
"omitSKID": true,
"omitOCSP": true,
"includeCRLDistributionPoints": true,
"maxValidityPeriod": "583200s",
"maxValidityBackdate": "1h5m",
"lintConfig": "test/config-next/zlint.toml",
"ignoredLints": [
"w_ext_subject_key_identifier_missing_sub_cert",
"e_dnsname_not_valid_tld"
]
}
},
@ -193,7 +200,8 @@
"tls-alpn-01": true
},
"identifiers": {
"dns": true
"dns": true,
"ip": true
}
},
"syslog": {

View File

@ -4,17 +4,19 @@
"dbConnectFile": "test/secrets/cert_checker_dburl",
"maxOpenConns": 10
},
"hostnamePolicyFile": "test/hostname-policy.yaml",
"hostnamePolicyFile": "test/ident-policy.yaml",
"workers": 16,
"unexpiredOnly": true,
"badResultsOnly": true,
"checkPeriod": "72h",
"acceptableValidityDurations": [
"7776000s"
"7776000s",
"160h"
],
"ignoredLints": [
"w_subject_common_name_included",
"w_ext_subject_key_identifier_missing_sub_cert",
"w_subject_common_name_included",
"e_dnsname_not_valid_tld",
"w_ext_subject_key_identifier_not_recommended_subscriber"
],
"ctLogListFile": "test/ct-test-srv/log_list.json"
@ -26,7 +28,8 @@
"tls-alpn-01": true
},
"identifiers": {
"dns": true
"dns": true,
"ip": true
}
},
"syslog": {

View File

@ -26,7 +26,7 @@
},
"maxContactsPerRegistration": 3,
"debugAddr": ":8002",
"hostnamePolicyFile": "test/hostname-policy.yaml",
"hostnamePolicyFile": "test/ident-policy.yaml",
"goodkey": {},
"issuerCerts": [
"test/certs/webpki/int-rsa-a.cert.pem",
@ -61,7 +61,8 @@
"orderLifetime": "7h",
"maxNames": 10,
"identifierTypes": [
"dns"
"dns",
"ip"
]
}
},
@ -193,7 +194,8 @@
"tls-alpn-01": true
},
"identifiers": {
"dns": true
"dns": true,
"ip": true
}
},
"syslog": {

View File

@ -1,5 +1,5 @@
#
# Example YAML Boulder hostname policy
# Example YAML Boulder identifier policy
#
# This is *not* a production ready policy file and not reflective of Let's
# Encrypt's policies! It is just an example.
@ -31,3 +31,13 @@ HighRiskBlockedNames:
# they are separated into their own list.
AdminBlockedNames:
- "sealand"
# AdminBlockedPrefixes is a list of IP address prefixes. All IP addresses
# contained within the prefix are blocked.
#
# To block a single IP, append "/32" for IPv4 or "/128" for IPv6.
AdminBlockedPrefixes:
- "64.112.117.66/32"
- "64.112.117.68/30"
- "2602:80a:6000:baa:ffff:ffff:ffff:ffff/128"
- "2602:80a:6000:bad::/64"

View File

@ -7,8 +7,9 @@ import (
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"fmt"
"os"
"net"
"strings"
"testing"
@ -212,22 +213,89 @@ func TestIPShortLived(t *testing.T) {
// Get one cert for the shortlived profile.
res, err := authAndIssue(client, key, idents, false, "shortlived")
if os.Getenv("BOULDER_CONFIG_DIR") == "test/config-next" {
if err != nil {
t.Errorf("issuing under shortlived profile: %s", err)
}
if res.Order.Profile != "shortlived" {
t.Errorf("got '%s' profile, wanted 'shortlived'", res.Order.Profile)
}
cert := res.certs[0]
if err != nil {
t.Errorf("issuing under shortlived profile: %s", err)
}
if res.Order.Profile != "shortlived" {
t.Errorf("got '%s' profile, wanted 'shortlived'", res.Order.Profile)
}
cert := res.certs[0]
// Check that the shortlived profile worked as expected.
if cert.IPAddresses[0].String() != ip {
t.Errorf("got cert with first IP SAN '%s', wanted '%s'", cert.IPAddresses[0], ip)
}
} else {
if !strings.Contains(err.Error(), "Profile \"shortlived\" does not permit ip type identifiers") {
t.Errorf("issuing under shortlived profile failed for the wrong reason: %s", err)
}
// Check that the shortlived profile worked as expected.
if cert.IPAddresses[0].String() != ip {
t.Errorf("got cert with first IP SAN '%s', wanted '%s'", cert.IPAddresses[0], ip)
}
}
// TestIPCNRejected verifies that we will reject IP address identifiers when
// they occur in the Subject CommonName.
func TestIPCNRejected(t *testing.T) {
t.Parallel()
// Create an account.
client, err := makeClient("mailto:example@letsencrypt.org")
if err != nil {
t.Fatalf("creating acme client: %s", err)
}
// Create an IP address identifier to request.
ip := "64.112.117.122"
ipParsed := net.ParseIP(ip)
idents := []acme.Identifier{
{Type: "ip", Value: ip},
}
order, err := client.Client.NewOrderExtension(client.Account, idents, acme.OrderExtension{Profile: "shortlived"})
if err != nil {
t.Fatalf("creating order: %s", err)
}
if len(order.Authorizations) != 1 {
t.Fatalf("Got %d authorizations, expected 1", len(order.Authorizations))
}
auth, err := client.Client.FetchAuthorization(client.Account, order.Authorizations[0])
chal, ok := auth.ChallengeMap[acme.ChallengeTypeHTTP01]
if !ok {
t.Fatalf("no HTTP challenge at %s", order.Authorizations[0])
}
_, err = testSrvClient.AddHTTP01Response(chal.Token, chal.KeyAuthorization)
if err != nil {
t.Fatalf("adding HTTP challenge response: %s", err)
}
defer testSrvClient.RemoveHTTP01Response(chal.Token)
chal, err = client.Client.UpdateChallenge(client.Account, chal)
if err != nil {
t.Fatalf("updating challenge: %s", err)
}
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
t.Fatalf("creating random cert key: %s", err)
}
csrTemplate := &x509.CertificateRequest{
Subject: pkix.Name{CommonName: ip},
SignatureAlgorithm: x509.ECDSAWithSHA256,
PublicKeyAlgorithm: x509.ECDSA,
PublicKey: key.Public(),
IPAddresses: []net.IP{ipParsed},
}
csrDer, err := x509.CreateCertificateRequest(rand.Reader, csrTemplate, key)
if err != nil {
t.Fatalf("making csr: %s", err)
}
csr, err := x509.ParseCertificateRequest(csrDer)
if err != nil {
t.Fatalf("parsing csr: %s", err)
}
_, err = client.Client.FinalizeOrder(client.Account, order, csr)
if err == nil {
t.Errorf("Finalizing order with IP in CN: got nil error, want badCSR error")
}
if !strings.Contains(err.Error(), "CSR contains IP address in Common Name") {
t.Errorf("issuing with IP in CN failed for the wrong reason: %s", err)
}
}

View File

@ -6,7 +6,6 @@ import (
"crypto/rand"
"encoding/hex"
"fmt"
"os"
"testing"
"github.com/eggsampler/acme/v3"
@ -16,12 +15,9 @@ import (
func TestDuplicateFQDNRateLimit(t *testing.T) {
t.Parallel()
idents := []acme.Identifier{{Type: "dns", Value: random_domain()}}
// TODO(#8235): Remove this conditional once IP address identifiers are
// enabled in test/config.
if os.Getenv("BOULDER_CONFIG_DIR") == "test/config-next" {
idents = append(idents, acme.Identifier{Type: "ip", Value: "64.112.117.122"})
idents := []acme.Identifier{
{Type: "dns", Value: random_domain()},
{Type: "ip", Value: "64.112.117.122"},
}
// The global rate limit for a duplicate certificates is 2 per 3 hours.

View File

@ -1,111 +0,0 @@
package main
import (
"fmt"
"io"
"log"
"net/http"
"strconv"
"strings"
)
// filter filters mails based on the To: and From: fields.
// The zero value matches all mails.
type filter struct {
To string
From string
}
func (f *filter) Match(m rcvdMail) bool {
if f.To != "" && f.To != m.To {
return false
}
if f.From != "" && f.From != m.From {
return false
}
return true
}
/*
/count - number of mails
/count?to=foo@bar.com - number of mails for foo@bar.com
/count?from=service@test.org - number of mails sent by service@test.org
/clear - clear the mail list
/mail/0 - first mail
/mail/1 - second mail
/mail/0?to=foo@bar.com - first mail for foo@bar.com
/mail/1?to=foo@bar.com - second mail for foo@bar.com
/mail/1?to=foo@bar.com&from=service@test.org - second mail for foo@bar.com from service@test.org
*/
func (srv *mailSrv) setupHTTP(serveMux *http.ServeMux) {
serveMux.HandleFunc("/count", srv.httpCount)
serveMux.HandleFunc("/clear", srv.httpClear)
serveMux.Handle("/mail/", http.StripPrefix("/mail/", http.HandlerFunc(srv.httpGetMail)))
}
func (srv *mailSrv) httpClear(w http.ResponseWriter, r *http.Request) {
if r.Method == "POST" {
srv.allMailMutex.Lock()
srv.allReceivedMail = nil
srv.allMailMutex.Unlock()
w.WriteHeader(200)
} else {
w.WriteHeader(405)
}
}
func (srv *mailSrv) httpCount(w http.ResponseWriter, r *http.Request) {
count := 0
srv.iterMail(extractFilter(r), func(m rcvdMail) bool {
count++
return false
})
fmt.Fprintf(w, "%d\n", count)
}
func (srv *mailSrv) httpGetMail(w http.ResponseWriter, r *http.Request) {
mailNum, err := strconv.Atoi(strings.Trim(r.URL.Path, "/"))
if err != nil {
w.WriteHeader(400)
log.Println("mail-test-srv: bad request:", r.URL.Path, "-", err)
return
}
idx := 0
found := srv.iterMail(extractFilter(r), func(m rcvdMail) bool {
if mailNum == idx {
printMail(w, m)
return true
}
idx++
return false
})
if !found {
w.WriteHeader(404)
}
}
func extractFilter(r *http.Request) filter {
values := r.URL.Query()
return filter{To: values.Get("to"), From: values.Get("from")}
}
func (srv *mailSrv) iterMail(f filter, cb func(rcvdMail) bool) bool {
srv.allMailMutex.Lock()
defer srv.allMailMutex.Unlock()
for _, v := range srv.allReceivedMail {
if !f.Match(v) {
continue
}
if cb(v) {
return true
}
}
return false
}
func printMail(w io.Writer, mail rcvdMail) {
fmt.Fprintf(w, "FROM %s\n", mail.From)
fmt.Fprintf(w, "TO %s\n", mail.To)
fmt.Fprintf(w, "\n%s\n", mail.Mail)
}

View File

@ -1,82 +0,0 @@
package main
import (
"bytes"
"fmt"
"io"
"net/http"
"net/http/httptest"
"strconv"
"strings"
"testing"
)
func reqAndRecorder(t testing.TB, method, relativeUrl string, body io.Reader) (*httptest.ResponseRecorder, *http.Request) {
endURL := fmt.Sprintf("http://localhost:9381%s", relativeUrl)
r, err := http.NewRequest(method, endURL, body)
if err != nil {
t.Fatalf("could not construct request: %v", err)
}
return httptest.NewRecorder(), r
}
func TestHTTPClear(t *testing.T) {
srv := mailSrv{}
w, r := reqAndRecorder(t, "POST", "/clear", nil)
srv.allReceivedMail = []rcvdMail{{}}
srv.httpClear(w, r)
if w.Code != 200 {
t.Errorf("expected 200, got %d", w.Code)
}
if len(srv.allReceivedMail) != 0 {
t.Error("/clear failed to clear mail buffer")
}
w, r = reqAndRecorder(t, "GET", "/clear", nil)
srv.allReceivedMail = []rcvdMail{{}}
srv.httpClear(w, r)
if w.Code != 405 {
t.Errorf("expected 405, got %d", w.Code)
}
if len(srv.allReceivedMail) != 1 {
t.Error("GET /clear cleared the mail buffer")
}
}
func TestHTTPCount(t *testing.T) {
srv := mailSrv{}
srv.allReceivedMail = []rcvdMail{
{From: "a", To: "b"},
{From: "a", To: "b"},
{From: "a", To: "c"},
{From: "c", To: "a"},
{From: "c", To: "b"},
}
tests := []struct {
URL string
Count int
}{
{URL: "/count", Count: 5},
{URL: "/count?to=b", Count: 3},
{URL: "/count?to=c", Count: 1},
}
var buf bytes.Buffer
for _, test := range tests {
w, r := reqAndRecorder(t, "GET", test.URL, nil)
buf.Reset()
w.Body = &buf
srv.httpCount(w, r)
if w.Code != 200 {
t.Errorf("%s: expected 200, got %d", test.URL, w.Code)
}
n, err := strconv.Atoi(strings.TrimSpace(buf.String()))
if err != nil {
t.Errorf("%s: expected a number, got '%s'", test.URL, buf.String())
} else if n != test.Count {
t.Errorf("%s: expected %d, got %d", test.URL, test.Count, n)
}
}
}

View File

@ -1,248 +0,0 @@
package main
import (
"bufio"
"bytes"
"context"
"crypto/tls"
"flag"
"fmt"
"log"
"net"
"net/http"
"net/mail"
"regexp"
"strings"
"sync"
"github.com/letsencrypt/boulder/cmd"
blog "github.com/letsencrypt/boulder/log"
)
type mailSrv struct {
closeFirst uint
allReceivedMail []rcvdMail
allMailMutex sync.Mutex
connNumber uint
connNumberMutex sync.RWMutex
logger blog.Logger
}
type rcvdMail struct {
From string
To string
Mail string
}
func expectLine(buf *bufio.Reader, expected string) error {
line, _, err := buf.ReadLine()
if err != nil {
return fmt.Errorf("readline: %v", err)
}
if string(line) != expected {
return fmt.Errorf("Expected %s, got %s", expected, line)
}
return nil
}
var mailFromRegex = regexp.MustCompile(`^MAIL FROM:<(.*)>\s*BODY=8BITMIME\s*$`)
var rcptToRegex = regexp.MustCompile(`^RCPT TO:<(.*)>\s*$`)
var smtpErr501 = []byte("501 syntax error in parameters or arguments \r\n")
var smtpOk250 = []byte("250 OK \r\n")
func (srv *mailSrv) handleConn(conn net.Conn) {
defer conn.Close()
srv.connNumberMutex.Lock()
srv.connNumber++
srv.connNumberMutex.Unlock()
srv.logger.Infof("mail-test-srv: Got connection from %s", conn.RemoteAddr())
readBuf := bufio.NewReader(conn)
conn.Write([]byte("220 smtp.example.com ESMTP\r\n"))
err := expectLine(readBuf, "EHLO localhost")
if err != nil {
log.Printf("mail-test-srv: %s: %v\n", conn.RemoteAddr(), err)
return
}
conn.Write([]byte("250-PIPELINING\r\n"))
conn.Write([]byte("250-AUTH PLAIN LOGIN\r\n"))
conn.Write([]byte("250 8BITMIME\r\n"))
// This AUTH PLAIN is the output of: echo -en '\0cert-manager@example.com\0password' | base64
// Must match the mail configs for integration tests.
err = expectLine(readBuf, "AUTH PLAIN AGNlcnQtbWFuYWdlckBleGFtcGxlLmNvbQBwYXNzd29yZA==")
if err != nil {
log.Printf("mail-test-srv: %s: %v\n", conn.RemoteAddr(), err)
return
}
conn.Write([]byte("235 2.7.0 Authentication successful\r\n"))
srv.logger.Infof("mail-test-srv: Successful auth from %s", conn.RemoteAddr())
// necessary commands:
// MAIL RCPT DATA QUIT
var fromAddr string
var toAddr []string
clearState := func() {
fromAddr = ""
toAddr = nil
}
reader := bufio.NewScanner(readBuf)
scan:
for reader.Scan() {
line := reader.Text()
cmdSplit := strings.SplitN(line, " ", 2)
cmd := cmdSplit[0]
switch cmd {
case "QUIT":
conn.Write([]byte("221 Bye \r\n"))
break scan
case "RSET":
clearState()
conn.Write(smtpOk250)
case "NOOP":
conn.Write(smtpOk250)
case "MAIL":
srv.connNumberMutex.RLock()
if srv.connNumber <= srv.closeFirst {
// Half of the time, close cleanly to simulate the server side closing
// unexpectedly.
if srv.connNumber%2 == 0 {
log.Printf(
"mail-test-srv: connection # %d < -closeFirst parameter %d, disconnecting client. Bye!\n",
srv.connNumber, srv.closeFirst)
clearState()
conn.Close()
} else {
// The rest of the time, simulate a stale connection timeout by sending
// a SMTP 421 message. This replicates the timeout/close from issue
// 2249 - https://github.com/letsencrypt/boulder/issues/2249
log.Printf(
"mail-test-srv: connection # %d < -closeFirst parameter %d, disconnecting with 421. Bye!\n",
srv.connNumber, srv.closeFirst)
clearState()
conn.Write([]byte("421 1.2.3 foo.bar.baz Error: timeout exceeded \r\n"))
conn.Close()
}
}
srv.connNumberMutex.RUnlock()
clearState()
matches := mailFromRegex.FindStringSubmatch(line)
if matches == nil {
log.Panicf("mail-test-srv: %s: MAIL FROM parse error\n", conn.RemoteAddr())
}
addr, err := mail.ParseAddress(matches[1])
if err != nil {
log.Panicf("mail-test-srv: %s: addr parse error: %v\n", conn.RemoteAddr(), err)
}
fromAddr = addr.Address
conn.Write(smtpOk250)
case "RCPT":
matches := rcptToRegex.FindStringSubmatch(line)
if matches == nil {
conn.Write(smtpErr501)
continue
}
addr, err := mail.ParseAddress(matches[1])
if err != nil {
log.Panicf("mail-test-srv: %s: addr parse error: %v\n", conn.RemoteAddr(), err)
}
toAddr = append(toAddr, addr.Address)
conn.Write(smtpOk250)
case "DATA":
conn.Write([]byte("354 Start mail input \r\n"))
var msgBuf bytes.Buffer
for reader.Scan() {
line := reader.Text()
msgBuf.WriteString(line)
msgBuf.WriteString("\r\n")
if strings.HasSuffix(msgBuf.String(), "\r\n.\r\n") {
break
}
}
if reader.Err() != nil {
log.Printf("mail-test-srv: read from %s: %v\n", conn.RemoteAddr(), reader.Err())
return
}
mailResult := rcvdMail{
From: fromAddr,
Mail: msgBuf.String(),
}
srv.allMailMutex.Lock()
for _, rcpt := range toAddr {
mailResult.To = rcpt
srv.allReceivedMail = append(srv.allReceivedMail, mailResult)
log.Printf("mail-test-srv: Got mail: %s -> %s\n", fromAddr, rcpt)
}
srv.allMailMutex.Unlock()
conn.Write([]byte("250 Got mail \r\n"))
clearState()
}
}
if reader.Err() != nil {
log.Printf("mail-test-srv: read from %s: %s\n", conn.RemoteAddr(), reader.Err())
}
}
func (srv *mailSrv) serveSMTP(ctx context.Context, l net.Listener) error {
for {
conn, err := l.Accept()
if err != nil {
// If the accept call returned an error because the listener has been
// closed, then the context should have been canceled too. In that case,
// ignore the error.
select {
case <-ctx.Done():
return nil
default:
return err
}
}
go srv.handleConn(conn)
}
}
func main() {
var listenAPI = flag.String("http", "0.0.0.0:9381", "http port to listen on")
var listenSMTP = flag.String("smtp", "0.0.0.0:9380", "smtp port to listen on")
var certFilename = flag.String("cert", "", "certificate to serve")
var privKeyFilename = flag.String("key", "", "private key for certificate")
var closeFirst = flag.Uint("closeFirst", 0, "close first n connections after MAIL for reconnection tests")
flag.Parse()
cert, err := tls.LoadX509KeyPair(*certFilename, *privKeyFilename)
if err != nil {
log.Fatal(err)
}
l, err := tls.Listen("tcp", *listenSMTP, &tls.Config{
Certificates: []tls.Certificate{cert},
})
if err != nil {
log.Fatalf("Couldn't bind %q for SMTP: %s", *listenSMTP, err)
}
defer l.Close()
srv := mailSrv{
closeFirst: *closeFirst,
logger: cmd.NewLogger(cmd.SyslogConfig{StdoutLevel: 7}),
}
srv.setupHTTP(http.DefaultServeMux)
go func() {
err := http.ListenAndServe(*listenAPI, http.DefaultServeMux) //nolint: gosec // No request timeout is fine for test-only code.
if err != nil {
log.Fatalln("Couldn't start HTTP server", err)
}
}()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go cmd.FailOnError(srv.serveSMTP(ctx, l), "Failed to accept connection")
cmd.WaitForSignal()
}

View File

@ -85,7 +85,7 @@ func (ts *testServer) checkToken(w http.ResponseWriter, r *http.Request) {
}
}
func (ts *testServer) createContactsHandler(w http.ResponseWriter, r *http.Request) {
func (ts *testServer) upsertContactsHandler(w http.ResponseWriter, r *http.Request) {
ts.checkToken(w, r)
businessUnitId := r.Header.Get("Pardot-Business-Unit-Id")
@ -100,19 +100,22 @@ func (ts *testServer) createContactsHandler(w http.ResponseWriter, r *http.Reque
return
}
type contactData struct {
Email string `json:"email"`
type upsertPayload struct {
MatchEmail string `json:"matchEmail"`
Prospect struct {
Email string `json:"email"`
} `json:"prospect"`
}
var contact contactData
err = json.Unmarshal(body, &contact)
var payload upsertPayload
err = json.Unmarshal(body, &payload)
if err != nil {
http.Error(w, "Failed to parse request body", http.StatusBadRequest)
return
}
if contact.Email == "" {
http.Error(w, "Missing 'email' field in request body", http.StatusBadRequest)
if payload.MatchEmail == "" || payload.Prospect.Email == "" {
http.Error(w, "Missing 'matchEmail' or 'prospect.email' in request body", http.StatusBadRequest)
return
}
@ -122,7 +125,7 @@ func (ts *testServer) createContactsHandler(w http.ResponseWriter, r *http.Reque
// with a small number of contacts, so it's fine.
ts.contacts.created = ts.contacts.created[1:]
}
ts.contacts.created = append(ts.contacts.created, contact.Email)
ts.contacts.created = append(ts.contacts.created, payload.Prospect.Email)
ts.contacts.Unlock()
w.Header().Set("Content-Type", "application/json")
@ -198,7 +201,7 @@ func main() {
// Pardot API Server
pardotMux := http.NewServeMux()
pardotMux.HandleFunc("/api/v5/objects/prospects", ts.createContactsHandler)
pardotMux.HandleFunc("/api/v5/objects/prospects/do/upsertLatestByEmail", ts.upsertContactsHandler)
pardotMux.HandleFunc("/contacts", ts.queryContactsHandler)
pardotServer := &http.Server{

View File

@ -50,10 +50,6 @@ SERVICES = (
8109, 9491, 'publisher.boulder',
('./bin/boulder', 'boulder-publisher', '--config', os.path.join(config_dir, 'publisher.json'), '--addr', ':9491', '--debug-addr', ':8109'),
None),
Service('mail-test-srv',
9380, None, None,
('./bin/mail-test-srv', '--closeFirst', '5', '--cert', 'test/certs/ipki/localhost/cert.pem', '--key', 'test/certs/ipki/localhost/key.pem'),
None),
Service('ocsp-responder',
8005, None, None,
('./bin/boulder', 'ocsp-responder', '--config', os.path.join(config_dir, 'ocsp-responder.json'), '--addr', ':4002', '--debug-addr', ':8005'),
@ -116,7 +112,7 @@ SERVICES = (
Service('bad-key-revoker',
8020, None, None,
('./bin/boulder', 'bad-key-revoker', '--config', os.path.join(config_dir, 'bad-key-revoker.json'), '--debug-addr', ':8020'),
('boulder-ra-1', 'boulder-ra-2', 'mail-test-srv')),
('boulder-ra-1', 'boulder-ra-2')),
# Note: the nonce-service instances bind to specific ports, not "all interfaces",
# because they use their explicitly bound port in calculating the nonce
# prefix, which is used by WFEs when deciding where to redeem nonces.

View File

@ -524,7 +524,7 @@ def test_highrisk_blocklist():
fail with a policy error.
"""
# We include "example.org" in `test/hostname-policy.yaml` in the
# We include "example.org" in `test/ident-policy.yaml` in the
# HighRiskBlockedNames list so issuing for "foo.example.org" should be
# blocked.
domain = "foo.example.org"
@ -538,7 +538,7 @@ def test_wildcard_exactblacklist():
should fail with a policy error.
"""
# We include "highrisk.le-test.hoffman-andrews.com" in `test/hostname-policy.yaml`
# We include "highrisk.le-test.hoffman-andrews.com" in `test/ident-policy.yaml`
# Issuing for "*.le-test.hoffman-andrews.com" should be blocked
domain = "*.le-test.hoffman-andrews.com"
# We expect this to produce a policy problem
@ -955,7 +955,7 @@ def test_new_order_policy_errs():
"""
client = chisel2.make_client(None)
# 'in-addr.arpa' is present in `test/hostname-policy.yaml`'s
# 'in-addr.arpa' is present in `test/ident-policy.yaml`'s
# HighRiskBlockedNames list.
csr_pem = chisel2.make_csr(["out-addr.in-addr.arpa", "between-addr.in-addr.arpa"])

View File

@ -90,7 +90,7 @@ func branch(args []string) error {
// components.
parts := strings.SplitN(tag, ".", 3)
if len(parts) != 3 {
return fmt.Errorf("failed to parse patch version from release tag %q", tag)
return fmt.Errorf("failed to parse release tag %q as semver", tag)
}
major := parts[0]
@ -101,7 +101,7 @@ func branch(args []string) error {
minor := parts[1]
t, err := time.Parse("20060102", minor)
if err != nil {
return fmt.Errorf("expected minor portion of release tag to be a ")
return fmt.Errorf("expected minor portion of release tag to be a date: %w", err)
}
if t.Year() < 2015 {
return fmt.Errorf("minor portion of release tag appears to be an unrealistic date: %q", t.String())

View File

@ -32,6 +32,7 @@ import (
"fmt"
"os"
"os/exec"
"strconv"
"strings"
"time"
)
@ -85,63 +86,105 @@ func tag(args []string) error {
return fmt.Errorf("invalid flags: %w", err)
}
if len(fs.Args()) > 1 {
var branch string
switch len(fs.Args()) {
case 0:
branch = "main"
case 1:
branch = fs.Arg(0)
if !strings.HasPrefix(branch, "release-branch-") {
return fmt.Errorf("branch must be 'main' or 'release-branch-...', got %q", branch)
}
default:
return fmt.Errorf("too many args: %#v", fs.Args())
}
branch := "main"
if len(fs.Args()) == 1 {
branch = fs.Arg(0)
}
switch {
case branch == "main":
break
case strings.HasPrefix(branch, "release-branch-"):
return fmt.Errorf("sorry, tagging hotfix release branches is not yet supported")
default:
return fmt.Errorf("branch must be 'main' or 'release-branch-...', got %q", branch)
}
// Fetch all of the latest commits on this ref from origin, so that we can
// ensure we're tagging the tip of the upstream branch.
// ensure we're tagging the tip of the upstream branch, and that we have all
// of the extant tags along this branch if its a release branch.
_, err = git("fetch", "origin", branch)
if err != nil {
return err
}
// We use semver's vMajor.Minor.Patch format, where the Major version is
// always 0 (no backwards compatibility guarantees), the Minor version is
// the date of the release, and the Patch number is zero for normal releases
// and only non-zero for hotfix releases.
minor := time.Now().Format("20060102")
version := fmt.Sprintf("v0.%s.0", minor)
message := fmt.Sprintf("Release %s", version)
var tag string
switch branch {
case "main":
tag = fmt.Sprintf("v0.%s.0", time.Now().Format("20060102"))
default:
tag, err = nextTagOnBranch(branch)
if err != nil {
return fmt.Errorf("failed to compute next hotfix tag: %w", err)
}
}
// Produce the tag, using -s to PGP sign it. This will fail if a tag with
// that name already exists.
_, err = git("tag", "-s", "-m", message, version, "origin/"+branch)
message := fmt.Sprintf("Release %s", tag)
_, err = git("tag", "-s", "-m", message, tag, "origin/"+branch)
if err != nil {
return err
}
// Show the result of the tagging operation, including the tag message and
// signature, and the commit hash and message, but not the diff.
out, err := git("show", "-s", version)
out, err := git("show", "-s", tag)
if err != nil {
return err
}
show(out)
if push {
_, err = git("push", "origin", version)
_, err = git("push", "origin", tag)
if err != nil {
return err
}
} else {
fmt.Println()
fmt.Println("Please inspect the tag above, then run:")
fmt.Printf(" git push origin %s\n", version)
fmt.Printf(" git push origin %s\n", tag)
}
return nil
}
func nextTagOnBranch(branch string) (string, error) {
baseVersion := strings.TrimPrefix(branch, "release-branch-")
out, err := git("tag", "--list", "--no-column", baseVersion+".*")
if err != nil {
return "", fmt.Errorf("failed to list extant tags on branch %q: %w", branch, err)
}
maxPatch := 0
for tag := range strings.SplitSeq(strings.TrimSpace(out), "\n") {
parts := strings.SplitN(tag, ".", 3)
if len(parts) != 3 {
return "", fmt.Errorf("failed to parse release tag %q as semver", tag)
}
major := parts[0]
if major != "v0" {
return "", fmt.Errorf("expected major portion of prior release tag %q to be 'v0'", tag)
}
minor := parts[1]
t, err := time.Parse("20060102", minor)
if err != nil {
return "", fmt.Errorf("expected minor portion of prior release tag %q to be a date: %w", tag, err)
}
if t.Year() < 2015 {
return "", fmt.Errorf("minor portion of prior release tag %q appears to be an unrealistic date: %q", tag, t.String())
}
patch := parts[2]
patchInt, err := strconv.Atoi(patch)
if err != nil {
return "", fmt.Errorf("patch portion of prior release tag %q is not an integer: %w", tag, err)
}
if patchInt > maxPatch {
maxPatch = patchInt
}
}
return fmt.Sprintf("%s.%d", baseVersion, maxPatch+1), nil
}

View File

@ -338,6 +338,10 @@ func (va *ValidationAuthorityImpl) extractRequestTarget(req *http.Request) (iden
reqIP, err := netip.ParseAddr(reqHost)
if err == nil {
// Reject IPv6 addresses with a scope zone (RFCs 4007 & 6874)
if reqIP.Zone() != "" {
return identifier.ACMEIdentifier{}, 0, berrors.ConnectionFailureError("Invalid host in redirect target: contains scope zone")
}
err := va.isReservedIPFunc(reqIP)
if err != nil {
return identifier.ACMEIdentifier{}, 0, berrors.ConnectionFailureError("Invalid host in redirect target: %s", err)

View File

@ -360,6 +360,14 @@ func TestExtractRequestTarget(t *testing.T) {
ExpectedError: fmt.Errorf("Invalid host in redirect target: " +
"IP address is in a reserved address block: [RFC9637]: Documentation"),
},
{
Name: "bare IPv6, scope zone",
Req: &http.Request{
URL: mustURL("http://[::1%25lo]"),
},
ExpectedError: fmt.Errorf("Invalid host in redirect target: " +
"contains scope zone"),
},
{
Name: "valid HTTP redirect, explicit port",
Req: &http.Request{

View File

@ -84,6 +84,10 @@ func (va *ValidationAuthorityImpl) tryGetChallengeCert(
return nil, nil, validationRecord, fmt.Errorf("can't parse IP address %q: %s", ident.Value, err)
}
addrs = []netip.Addr{netIP}
// This field shouldn't be necessary: it's redundant with the Hostname
// field. But Challenge.RecordsSane expects it, and there's no easy way to
// special-case IP identifiers within that function.
validationRecord.AddressesResolved = addrs
default:
// This should never happen. The calling function should check the
// identifier type.

View File

@ -368,10 +368,13 @@ func TestTLSALPN01SuccessDNS(t *testing.T) {
va, _ := setup(hs, "", nil, nil)
_, err := va.validateTLSALPN01(ctx, identifier.NewDNS("expected"), expectedKeyAuthorization)
res, err := va.validateTLSALPN01(ctx, identifier.NewDNS("expected"), expectedKeyAuthorization)
if err != nil {
t.Errorf("Validation failed: %v", err)
}
if !(core.Challenge{Type: core.ChallengeTypeTLSALPN01, ValidationRecord: res}).RecordsSane() {
t.Errorf("got validation record %#v, but want something sane", res)
}
test.AssertMetricWithLabelsEquals(
t, va.metrics.tlsALPNOIDCounter, prometheus.Labels{"oid": IdPeAcmeIdentifier.String()}, 1)
@ -384,10 +387,13 @@ func TestTLSALPN01SuccessIPv4(t *testing.T) {
va, _ := setup(hs, "", nil, nil)
_, err := va.validateTLSALPN01(ctx, identifier.NewIP(netip.MustParseAddr("127.0.0.1")), expectedKeyAuthorization)
res, err := va.validateTLSALPN01(ctx, identifier.NewIP(netip.MustParseAddr("127.0.0.1")), expectedKeyAuthorization)
if err != nil {
t.Errorf("Validation failed: %v", err)
}
if !(core.Challenge{Type: core.ChallengeTypeTLSALPN01, ValidationRecord: res}).RecordsSane() {
t.Errorf("got validation record %#v, but want something sane", res)
}
test.AssertMetricWithLabelsEquals(
t, va.metrics.tlsALPNOIDCounter, prometheus.Labels{"oid": IdPeAcmeIdentifier.String()}, 1)
@ -400,10 +406,13 @@ func TestTLSALPN01SuccessIPv6(t *testing.T) {
va, _ := setup(hs, "", nil, nil)
_, err := va.validateTLSALPN01(ctx, identifier.NewIP(netip.MustParseAddr("::1")), expectedKeyAuthorization)
res, err := va.validateTLSALPN01(ctx, identifier.NewIP(netip.MustParseAddr("::1")), expectedKeyAuthorization)
if err != nil {
t.Errorf("Validation failed: %v", err)
}
if !(core.Challenge{Type: core.ChallengeTypeTLSALPN01, ValidationRecord: res}).RecordsSane() {
t.Errorf("got validation record %#v, but want something sane", res)
}
test.AssertMetricWithLabelsEquals(
t, va.metrics.tlsALPNOIDCounter, prometheus.Labels{"oid": IdPeAcmeIdentifier.String()}, 1)

View File

@ -1,3 +1,11 @@
# v1.83.0 (2025-07-02)
* **Feature**: Added support for directory bucket creation with tags and bucket ARN retrieval in CreateBucket, ListDirectoryBuckets, and HeadBucket operations
# v1.82.0 (2025-06-25)
* **Feature**: Adds support for additional server-side encryption mode and storage class values for accessing Amazon FSx data from Amazon S3 using S3 Access Points
# v1.81.0 (2025-06-18)
* **Feature**: Added support for renaming objects within the same bucket using the new RenameObject API.

View File

@ -465,8 +465,10 @@ type CompleteMultipartUploadOutput struct {
// If present, indicates the ID of the KMS key that was used for object encryption.
SSEKMSKeyId *string
// The server-side encryption algorithm used when storing this object in Amazon S3
// (for example, AES256 , aws:kms ).
// The server-side encryption algorithm used when storing this object in Amazon S3.
//
// When accessing data stored in Amazon FSx file systems using S3 access points,
// the only valid server side encryption option is aws:fsx .
ServerSideEncryption types.ServerSideEncryption
// Version ID of the newly created object, in case the bucket has versioning

View File

@ -695,6 +695,13 @@ type CopyObjectInput struct {
// the same customer managed key that you specified for the directory bucket's
// default encryption configuration.
//
// - S3 access points for Amazon FSx - When accessing data stored in Amazon FSx
// file systems using S3 access points, the only valid server side encryption
// option is aws:fsx . All Amazon FSx file systems have encryption configured by
// default and are encrypted at rest. Data is automatically encrypted before being
// written to the file system, and automatically decrypted as it is read. These
// processes are handled transparently by Amazon FSx.
//
// [Using Server-Side Encryption]: https://docs.aws.amazon.com/AmazonS3/latest/dev/serv-side-encryption.html
// [Specifying server-side encryption with KMS for new object uploads]: https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-express-specifying-kms-encryption.html
// [customer managed key]: https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#customer-cmk
@ -896,7 +903,10 @@ type CopyObjectOutput struct {
SSEKMSKeyId *string
// The server-side encryption algorithm used when you store this object in Amazon
// S3 (for example, AES256 , aws:kms , aws:kms:dsse ).
// S3 or Amazon FSx.
//
// When accessing data stored in Amazon FSx file systems using S3 access points,
// the only valid server side encryption option is aws:fsx .
ServerSideEncryption types.ServerSideEncryption
// Version ID of the newly created copy.

View File

@ -268,6 +268,15 @@ func (in *CreateBucketInput) bindEndpointParams(p *EndpointParameters) {
type CreateBucketOutput struct {
// The Amazon Resource Name (ARN) of the S3 bucket. ARNs uniquely identify Amazon
// Web Services resources across all of Amazon Web Services.
//
// This parameter is only supported for S3 directory buckets. For more
// information, see [Using tags with directory buckets].
//
// [Using tags with directory buckets]: https://docs.aws.amazon.com/AmazonS3/latest/userguide/directory-buckets-tagging.html
BucketArn *string
// A forward slash followed by the name of the bucket.
Location *string

View File

@ -686,7 +686,7 @@ type CreateMultipartUploadInput struct {
SSEKMSKeyId *string
// The server-side encryption algorithm used when you store this object in Amazon
// S3 (for example, AES256 , aws:kms ).
// S3 or Amazon FSx.
//
// - Directory buckets - For directory buckets, there are only two supported
// options for server-side encryption: server-side encryption with Amazon S3
@ -719,6 +719,13 @@ type CreateMultipartUploadInput struct {
// request headers must match the default encryption configuration of the directory
// bucket.
//
// - S3 access points for Amazon FSx - When accessing data stored in Amazon FSx
// file systems using S3 access points, the only valid server side encryption
// option is aws:fsx . All Amazon FSx file systems have encryption configured by
// default and are encrypted at rest. Data is automatically encrypted before being
// written to the file system, and automatically decrypted as it is read. These
// processes are handled transparently by Amazon FSx.
//
// [Specifying server-side encryption with KMS for new object uploads]: https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-express-specifying-kms-encryption.html
// [Protecting data with server-side encryption]: https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-express-serv-side-encryption.html
// [CopyObject]: https://docs.aws.amazon.com/AmazonS3/latest/API/API_CopyObject.html
@ -838,7 +845,10 @@ type CreateMultipartUploadOutput struct {
SSEKMSKeyId *string
// The server-side encryption algorithm used when you store this object in Amazon
// S3 (for example, AES256 , aws:kms ).
// S3 or Amazon FSx.
//
// When accessing data stored in Amazon FSx file systems using S3 access points,
// the only valid server side encryption option is aws:fsx .
ServerSideEncryption types.ServerSideEncryption
// ID for the initiated multipart upload.

View File

@ -219,6 +219,13 @@ type CreateSessionInput struct {
// Amazon S3 encrypts data with SSE-S3. For more information, see [Protecting data with server-side encryption]in the Amazon S3
// User Guide.
//
// S3 access points for Amazon FSx - When accessing data stored in Amazon FSx file
// systems using S3 access points, the only valid server side encryption option is
// aws:fsx . All Amazon FSx file systems have encryption configured by default and
// are encrypted at rest. Data is automatically encrypted before being written to
// the file system, and automatically decrypted as it is read. These processes are
// handled transparently by Amazon FSx.
//
// [Protecting data with server-side encryption]: https://docs.aws.amazon.com/AmazonS3/latest/userguide/serv-side-encryption.html
ServerSideEncryption types.ServerSideEncryption
@ -264,6 +271,9 @@ type CreateSessionOutput struct {
// The server-side encryption algorithm used when you store objects in the
// directory bucket.
//
// When accessing data stored in Amazon FSx file systems using S3 access points,
// the only valid server side encryption option is aws:fsx .
ServerSideEncryption types.ServerSideEncryption
// Metadata pertaining to the operation's result.

Some files were not shown because too many files have changed in this diff Show More