@@ -2,10 +2,12 @@ package sa
22
33import (
44 "context"
5+ "crypto/rand"
56 "crypto/sha256"
67 "crypto/x509"
78 "database/sql"
89 "encoding/base64"
10+ "encoding/binary"
911 "encoding/json"
1012 "errors"
1113 "fmt"
@@ -17,6 +19,7 @@ import (
1719 "time"
1820
1921 "github.com/go-jose/go-jose/v4"
22+ "github.com/jmhodges/clock"
2023 "google.golang.org/protobuf/types/known/timestamppb"
2124
2225 "github.com/letsencrypt/boulder/core"
@@ -59,6 +62,54 @@ func badJSONError(msg string, jsonData []byte, err error) error {
5962 }
6063}
6164
65+ // newRandomID creates a 64-bit mostly-random number to be used as the
66+ // unique ID column in a table which no longer uses auto_increment IDs. It takes
67+ // the clock as an argument so that it can include the current "epoch" as the
68+ // first byte of the ID, for the sake of easily dropping old data.
69+ func newRandomID (clk clock.Clock ) (int64 , error ) {
70+ idBytes := make ([]byte , 8 ) // 8 bytes is 64 bits
71+
72+ // Read random bits into the lower 7 bytes of the id.
73+ _ , err := rand .Read (idBytes [1 :])
74+ if err != nil {
75+ return 0 , fmt .Errorf ("while generating unique database id: %w" , err )
76+ }
77+
78+ // Epochs are arbitrarily chosen to be 90 day chunks counting from the start
79+ // of 2024. This gives us 127 * 90 = ~31 years worth of epochs before we have
80+ // to worry about a rollover.
81+ epoch := uint8 (clk .Now ().Sub (time .Date (2024 , 01 , 01 , 00 , 00 , 00 , 00 , time .UTC )) / (90 * 24 * time .Hour ))
82+ if epoch & 0x80 != 0 {
83+ // If the first bit is a 1, either the current date is before the epoch
84+ // start date, or we've gone too far into the future. Error out before we
85+ // accidentally generate a negative ID.
86+ return 0 , fmt .Errorf ("invalid epoch: %d" , epoch )
87+ }
88+ idBytes [0 ] = epoch
89+
90+ id := binary .BigEndian .Uint64 (idBytes )
91+ return int64 (id ), nil
92+ }
93+
94+ // looksLikeRandomID returns true if the input ID looks like it might belong to
95+ // the new schema which uses epoch-prefixed random IDs instead of auto-increment
96+ // columns. This is only necessary during the migration period when we are
97+ // reading from both the old and new schemas simultaneously.
98+ func looksLikeRandomID (id int64 , clk clock.Clock ) bool {
99+ // Compute the current and previous epochs. If the input ID starts with one of
100+ // those two epochs, it's one of ours. Otherwise, it came from somewhere
101+ // unknown and we should ask the old schema about it just in case.
102+ currEpoch := uint8 (clk .Now ().Sub (time .Date (2024 , 01 , 01 , 00 , 00 , 00 , 00 , time .UTC )) / (90 * 24 * time .Hour ))
103+ prevEpoch := uint8 (clk .Now ().Add (- 90 * 24 * time .Hour ).Sub (time .Date (2024 , 01 , 01 , 00 , 00 , 00 , 00 , time .UTC )) / (90 * 24 * time .Hour ))
104+
105+ buf := make ([]byte , 8 )
106+ binary .BigEndian .PutUint64 (buf , uint64 (id ))
107+ if buf [0 ] == currEpoch || buf [0 ] == prevEpoch {
108+ return true
109+ }
110+ return false
111+ }
112+
62113const regFields = "id, jwk, jwk_sha256, contact, agreement, initialIP, createdAt, LockCol, status"
63114
64115// ClearEmail removes the provided email address from one specified registration. If
@@ -1412,3 +1463,47 @@ type pausedModel struct {
14121463 PausedAt time.Time `db:"pausedAt"`
14131464 UnpausedAt * time.Time `db:"unpausedAt"`
14141465}
1466+
1467+ // orders2Model represents a row in the "orders2" table.
1468+ type orders2Model struct {
1469+ ID int64
1470+ RegistrationID int64
1471+ Created time.Time
1472+ Expires time.Time
1473+ AuthorizationIDs []int64 // Actually a JSON list of ints
1474+ Profile string
1475+ BeganProcessing bool
1476+ Error []byte
1477+ CertificateSerial string
1478+ }
1479+
1480+ // authorizationsModel represents a row in the "authorizations" table.
1481+ type authorizationsModel struct {
1482+ ID int64
1483+ RegistrationID int64
1484+ IdentifierType uint8
1485+ IdentifierValue string
1486+ Created time.Time
1487+ Expires time.Time
1488+ Profile string
1489+ Challenges uint8
1490+ Token []byte
1491+ Status uint8
1492+ ValidationIDs []int64 // Actually a JSON list of ints
1493+ }
1494+
1495+ // validationsModel represents a row in the "validations" table.
1496+ type validationsModel struct {
1497+ ID int64
1498+ Challenge uint8
1499+ AttemptedAt time.Time
1500+ Status uint8
1501+ Record string
1502+ }
1503+
1504+ // authzReuseModel represents a row in the "authzReuse" table.
1505+ type authzReuseModel struct {
1506+ ID int64 `db:"accountID_identifier"`
1507+ AuthzID int64
1508+ Expires time.Time
1509+ }
0 commit comments