Skip to content

Commit 366942a

Browse files
trees/issuancelog: Add a shared log identity configuration
1 parent 9ae976c commit 366942a

13 files changed

Lines changed: 215 additions & 137 deletions

File tree

cmd/boulder-mtca/main.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"github.com/letsencrypt/boulder/issuance"
2323
mtca "github.com/letsencrypt/boulder/mtca"
2424
mtcapb "github.com/letsencrypt/boulder/mtca/proto"
25+
"github.com/letsencrypt/boulder/trees/issuancelog"
2526
)
2627

2728
type Config struct {
@@ -33,9 +34,13 @@ type Config struct {
3334
DB cmd.DBConfig `validate:"required"`
3435
S3 bs3.Config `validate:"required"`
3536

37+
// LogID identifies the issuance log this MTCA sequences. Its CA ID must
38+
// match the issuer certificate's.
39+
LogID issuancelog.ID `validate:"required"`
40+
3641
Issuance struct {
3742
CertProfiles map[string]issuance.ProfileConfig `validate:"required,dive,keys,alphanum,min=1,max=32,endkeys"`
38-
// Issuers holds the configuration for a single MTCA instance with a single mtcaID.
43+
// Issuers holds the configuration for a single MTCA instance with a single CA ID.
3944
// We run a separate process for each issuer.
4045
// TODO: the issuance package parses the CA certificate as a self-signed X.509
4146
// certificate, but per MTC draft, a CA SHOULD be represented by an RFC 9925
@@ -115,6 +120,7 @@ func main() {
115120
mtcaImpl, err := mtca.New(
116121
issuer,
117122
profiles,
123+
c.MTCA.LogID,
118124
c.MTCA.SequencingPeriod.Duration,
119125
dbMap,
120126
s3c,

cmd/boulder-mtpublisher/main.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"github.com/letsencrypt/boulder/mtpublisher"
1717
"github.com/letsencrypt/boulder/privatekey"
1818
"github.com/letsencrypt/boulder/sa"
19+
"github.com/letsencrypt/boulder/trees/issuancelog"
1920
)
2021

2122
type Config struct {
@@ -28,10 +29,9 @@ type Config struct {
2829
// lack a mirror cosignature.
2930
PollInterval config.Duration `validate:"required"`
3031

31-
// MTCLogID is the log this MTPublisher operates on (e.g.
32-
// "44947.4.1.0.44"). Used as a guard on the `mtcLogID` column of the
33-
// connected checkpoints table.
34-
MTCLogID string `validate:"required"`
32+
// LogID identifies the issuance log this publisher operates on. It must
33+
// match the mtca's.
34+
LogID issuancelog.ID `validate:"required"`
3535

3636
// MirrorID identifies the cosigner this publisher writes alongside each
3737
// cosignature (e.g. "32473.9").
@@ -99,7 +99,7 @@ func main() {
9999
pubKey, err := loadMLDSAPublicKey(c.MTPublisher.MirrorPublicKeyFile)
100100
cmd.FailOnError(err, "Loading cosigner public key")
101101

102-
publisher, err := mtpublisher.New(dbMap, c.MTPublisher.PollInterval.Duration, c.MTPublisher.MTCLogID, c.MTPublisher.MirrorID, signer, pubKey, logger)
102+
publisher, err := mtpublisher.New(dbMap, c.MTPublisher.PollInterval.Duration, c.MTPublisher.LogID, c.MTPublisher.MirrorID, signer, pubKey, logger)
103103
cmd.FailOnError(err, "Failed to create MTPublisher stub")
104104

105105
ctx, cancel := context.WithCancel(context.Background())

mtca/mtca.go

Lines changed: 34 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
mtcapb "github.com/letsencrypt/boulder/mtca/proto"
3030
"github.com/letsencrypt/boulder/trees/cosignature"
3131
"github.com/letsencrypt/boulder/trees/entry"
32+
"github.com/letsencrypt/boulder/trees/issuancelog"
3233
"github.com/letsencrypt/boulder/trees/tiles"
3334
"golang.org/x/mod/sumdb/tlog"
3435
)
@@ -42,16 +43,20 @@ var _ mtcapb.MTCAServer = &mtca{}
4243
func New(
4344
issuer *issuance.Issuer,
4445
profiles map[string]*issuance.Profile,
46+
logID issuancelog.ID,
4547
sequencingPeriod time.Duration,
4648
dbMap *borp.DbMap,
4749
s3c simpleS3,
4850
logger blog.Logger,
4951
clk clock.Clock,
5052
) (*mtca, error) {
51-
mtcaID, err := getMTCAID(issuer.Cert.Certificate)
53+
certCAID, err := getCAID(issuer.Cert.Certificate)
5254
if err != nil {
5355
return nil, err
5456
}
57+
if certCAID != logID.CAID {
58+
return nil, fmt.Errorf("configured CA ID %q does not match issuer certificate CA ID %q", logID.CAID, certCAID)
59+
}
5560

5661
if sequencingPeriod == 0 {
5762
return nil, errors.New("sequencingPeriod must be non-zero")
@@ -60,10 +65,8 @@ func New(
6065
m := &mtca{
6166
issuer: issuer,
6267
profiles: profiles,
63-
mtcaID: mtcaID,
64-
// TODO: collect this from config
65-
logNumber: 44,
66-
pool: &pool{maxSize: 100},
68+
logID: logID,
69+
pool: &pool{maxSize: 100},
6770

6871
sequencingPeriod: sequencingPeriod,
6972

@@ -73,7 +76,7 @@ func New(
7376
clk: clk,
7477
}
7578

76-
cosigner, err := cosignature.NewCosigner(mtcaID, m.mtcLogID(), issuer.Signer)
79+
cosigner, err := cosignature.NewCosigner(logID.CAID, logID.Origin(), issuer.Signer)
7780
if err != nil {
7881
return nil, fmt.Errorf("creating CA cosigner: %s", err)
7982
}
@@ -83,7 +86,7 @@ func New(
8386
if !ok {
8487
return nil, fmt.Errorf("issuer public key is %T, must be ML-DSA-44", issuer.Signer.Public())
8588
}
86-
verifier, err := cosignature.NewVerifier(mtcaID, pubKey)
89+
verifier, err := cosignature.NewVerifier(logID.CAID, pubKey)
8790
if err != nil {
8891
return nil, fmt.Errorf("creating CA verifier: %s", err)
8992
}
@@ -97,12 +100,11 @@ type mtca struct {
97100

98101
issuer *issuance.Issuer
99102
profiles map[string]*issuance.Profile
100-
mtcaID string
103+
logID issuancelog.ID
101104
cosigner *cosignature.Cosigner
102105
verifier *cosignature.Verifier
103106

104-
logNumber uint16
105-
pool *pool
107+
pool *pool
106108

107109
// frontier contains all the tiles on the right edge of the tree.
108110
// It will be used to accumulate entries for writing to storage.
@@ -128,15 +130,15 @@ type simpleS3 interface {
128130
Bucket() string
129131
}
130132

131-
func getMTCAID(issuerCert *x509.Certificate) (string, error) {
133+
func getCAID(issuerCert *x509.Certificate) (string, error) {
132134
testingTrustAnchorIDOID := asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 44363, 47, 1}
133135
for _, attribute := range issuerCert.Subject.Names {
134136
if attribute.Type.Equal(testingTrustAnchorIDOID) {
135-
mtcaID, ok := attribute.Value.(string)
137+
caID, ok := attribute.Value.(string)
136138
if !ok {
137139
return "", fmt.Errorf("invalid trust anchor attribute type %T", attribute.Value)
138140
}
139-
return mtcaID, nil
141+
return caID, nil
140142
}
141143
}
142144

@@ -165,14 +167,14 @@ func (m *mtca) InitLog(ctx context.Context) error {
165167
_, err = db.WithTransaction(ctx, m.db, func(tx db.Executor) (any, error) {
166168
var numLatestCheckpoints int64
167169
err := tx.SelectOne(ctx, &numLatestCheckpoints, "SELECT COUNT(*) FROM latestCheckpoint WHERE mtcLogID = ?",
168-
m.mtcLogID())
170+
m.logID.String())
169171
if err != nil {
170172
return nil, fmt.Errorf("getting latestCheckpoint: %s", err)
171173
}
172174

173175
var numCheckpoints int64
174176
err = tx.SelectOne(ctx, &numCheckpoints, "SELECT COUNT(*) FROM checkpoints WHERE mtcLogID = ?",
175-
m.mtcLogID())
177+
m.logID.String())
176178
if err != nil {
177179
return nil, fmt.Errorf("getting checkpoints: %s", err)
178180
}
@@ -183,11 +185,11 @@ func (m *mtca) InitLog(ctx context.Context) error {
183185
}
184186

185187
return nil, fmt.Errorf("initializing issuance log for %s: already has %d checkpoints and %d latestCheckpoint rows",
186-
m.mtcLogID(), numCheckpoints, numLatestCheckpoints)
188+
m.logID.String(), numCheckpoints, numLatestCheckpoints)
187189
}
188190

189191
firstCheckpoint := checkpoint{
190-
MTCLogID: m.mtcLogID(),
192+
MTCLogID: m.logID.String(),
191193
TreeSize: candidate.TreeSize(),
192194
RootHash: rootHash[:],
193195
}
@@ -205,7 +207,7 @@ func (m *mtca) InitLog(ctx context.Context) error {
205207
}
206208

207209
_, err = tx.ExecContext(ctx, "INSERT INTO latestCheckpoint (id, mtcLogID) VALUES (?, ?)",
208-
firstCheckpoint.ID, m.mtcLogID())
210+
firstCheckpoint.ID, m.logID.String())
209211
if err != nil {
210212
return nil, fmt.Errorf("inserting latestCheckpoint: %s", err)
211213
}
@@ -224,7 +226,7 @@ func (m *mtca) InitLog(ctx context.Context) error {
224226
return err
225227
}
226228

227-
err = candidate.Publish(ctx, m.s3c, m.tileStoragePrefix())
229+
err = candidate.Publish(ctx, m.s3c, m.logID.TilePrefix())
228230
if err != nil {
229231
return err
230232
}
@@ -246,7 +248,7 @@ func (m *mtca) Preflight(ctx context.Context) error {
246248
if err != nil {
247249
return err
248250
}
249-
frontier, err := tiles.LoadFrontier(ctx, m.s3c, latest.TreeSize, m.tileStoragePrefix())
251+
frontier, err := tiles.LoadFrontier(ctx, m.s3c, latest.TreeSize, m.logID.TilePrefix())
250252
if err != nil {
251253
return err
252254
}
@@ -299,32 +301,6 @@ func (p *pool) append(e pendingEntry) error {
299301
return nil
300302
}
301303

302-
// mtcLogID returns the string-formatted relative OID for this log.
303-
// The .0. arc relative to the MTCA ID contains log numbers.
304-
// https://ietf-plants-wg.github.io/merkle-tree-certs/draft-ietf-plants-merkle-tree-certs.html#ca-ids
305-
func (m *mtca) mtcLogID() string {
306-
return fmt.Sprintf("%s.0.%d", m.mtcaID, m.logNumber)
307-
}
308-
309-
// tileStoragePrefix returns the path within a bucket where we will store tiles.
310-
//
311-
// https://github.com/C2SP/C2SP/blob/main/mtc-tlog.md#serving-issuance-logs
312-
//
313-
// "Each log's prefix URL is the concatenation of the CA prefix URL and the log number, encoded as an
314-
// ASCII decimal integer with no additional leading zeros:
315-
//
316-
// <CA prefix URL>/<log number>"
317-
//
318-
// We assume we will serve directly from tile storage (likely sync'ed somewhere), so we
319-
// want to store tiles compatible with that pattern, with log number as the last component
320-
// of the path before "tile/".
321-
//
322-
// As a matter of local convention we will put the MTCA ID as the path component right before
323-
// log number.
324-
func (m *mtca) tileStoragePrefix() string {
325-
return fmt.Sprintf("%s/%d", m.mtcaID, m.logNumber)
326-
}
327-
328304
// Issue requests a TBSCertificateLogEntry be issued and returns after it's been sequenced into the log
329305
// and a new checkpoint signed by the CA. It does not wait for a mirror cosignature.
330306
//
@@ -386,7 +362,7 @@ func (m *mtca) Issue(ctx context.Context, req *mtcapb.IssueRequest) (*mtcapb.Iss
386362
return nil, errors.New("error during sequencing")
387363
}
388364
return &mtcapb.IssueResponse{
389-
MtcLogID: m.mtcLogID(),
365+
MtcLogID: m.logID.String(),
390366
MtcEntryIndex: entryIndex,
391367
}, nil
392368
}
@@ -492,7 +468,7 @@ func (m *mtca) sequence(ctx context.Context) error {
492468
m.log.AuditInfo("issuing", map[string]any{
493469
"TBSCertificateLogEntry": hex.EncodeToString(e.mtcle.TBS()),
494470
"entryIndex": latest.TreeSize + int64(i),
495-
"mtcLogID": m.mtcLogID(),
471+
"mtcLogID": m.logID.String(),
496472
"newRootHash": newRootHash.String(),
497473
})
498474
}
@@ -501,14 +477,14 @@ func (m *mtca) sequence(ctx context.Context) error {
501477
// (but before publishing a new checkpoint signed note), we will flush
502478
// to the live location. This ensures we've persisted the tiles before
503479
// committing to a tree hash by signing it.
504-
err = candidate.Stage(ctx, m.s3c, m.tileStoragePrefix())
480+
err = candidate.Stage(ctx, m.s3c, m.logID.TilePrefix())
505481
if err != nil {
506482
return fmt.Errorf("staging candidate tiles: %s", err)
507483
}
508484

509485
newCheckpoint := checkpoint{
510486
ID: 0,
511-
MTCLogID: m.mtcLogID(),
487+
MTCLogID: m.logID.String(),
512488
MTCASignature: nil,
513489
MirrorID: "",
514490
MirrorSignature: nil,
@@ -541,7 +517,7 @@ func (m *mtca) sequence(ctx context.Context) error {
541517
// https://mariadb.com/docs/server/reference/sql-statements/data-manipulation/selecting-data/for-update
542518
err := tx.SelectOne(ctx, &latestID,
543519
`SELECT id from latestCheckpoint WHERE mtcLogID = ? FOR UPDATE`,
544-
m.mtcLogID())
520+
m.logID.String())
545521
if err != nil {
546522
return nil, err
547523
}
@@ -558,7 +534,7 @@ func (m *mtca) sequence(ctx context.Context) error {
558534
}
559535

560536
result, err := tx.ExecContext(ctx, "UPDATE checkpoints SET mtcaSignature = ? WHERE mtcLogID = ? AND id = ?",
561-
caSig, m.mtcLogID(), newCheckpoint.ID)
537+
caSig, m.logID.String(), newCheckpoint.ID)
562538
if err != nil {
563539
return nil, fmt.Errorf("updating checkpoint: %s", err)
564540
}
@@ -571,7 +547,7 @@ func (m *mtca) sequence(ctx context.Context) error {
571547
}
572548

573549
result, err = tx.ExecContext(ctx, "UPDATE latestCheckpoint SET id = ? WHERE mtcLogID = ? AND id = ?",
574-
newCheckpoint.ID, m.mtcLogID(), latestID)
550+
newCheckpoint.ID, m.logID.String(), latestID)
575551
if err != nil {
576552
return nil, fmt.Errorf("updating latestCheckpoint: %s", err)
577553
}
@@ -599,7 +575,7 @@ func (m *mtca) sequence(ctx context.Context) error {
599575
// Once we add publishing of checkpoints as signed notes, publication of the signed note
600576
// should come after this flush succeeds, so monitors don't try to fetch tiles that aren't
601577
// yet available.
602-
err = m.frontier.Publish(ctx, m.s3c, m.tileStoragePrefix())
578+
err = m.frontier.Publish(ctx, m.s3c, m.logID.TilePrefix())
603579
if err != nil {
604580
return fmt.Errorf("publishing tiles: %s", err)
605581
}
@@ -671,13 +647,13 @@ func (m *mtca) latestCheckpoint(ctx context.Context) (*checkpoint, error) {
671647
USING(id)
672648
WHERE latestCheckpoint.mtcLogID = ? AND
673649
checkpoints.mtcLogID = ?`,
674-
m.mtcLogID(),
675-
m.mtcLogID())
650+
m.logID.String(),
651+
m.logID.String())
676652
if err != nil {
677653
if errors.Is(err, sql.ErrNoRows) {
678-
return nil, fmt.Errorf("getting latest checkpoint for %q: issuance log DB is not initialized", m.mtcLogID())
654+
return nil, fmt.Errorf("getting latest checkpoint for %q: issuance log DB is not initialized", m.logID.String())
679655
}
680-
return nil, fmt.Errorf("getting latest checkpoint for %q: %w", m.mtcLogID(), err)
656+
return nil, fmt.Errorf("getting latest checkpoint for %q: %w", m.logID.String(), err)
681657
}
682658

683659
return &latest, nil

0 commit comments

Comments
 (0)