@@ -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"
@@ -59,6 +61,54 @@ func badJSONError(msg string, jsonData []byte, err error) error {
5961 }
6062}
6163
64+ // newRandomID creates a 64-bit mostly-random number to be used as the unique ID
65+ // column in a table which no longer uses auto_increment IDs. It takes the
66+ // current time as an argument so that it can include the current "epoch" as the
67+ // first byte of the ID, for the sake of easily dropping old data.
68+ func newRandomID (now time.Time ) (int64 , error ) {
69+ idBytes := make ([]byte , 8 ) // 8 bytes is 64 bits
70+
71+ // Read random bits into the lower 7 bytes of the id.
72+ _ , err := rand .Read (idBytes [1 :])
73+ if err != nil {
74+ return 0 , fmt .Errorf ("while generating unique database id: %w" , err )
75+ }
76+
77+ // Epochs are arbitrarily chosen to be 90 day chunks counting from the start
78+ // of 2024. This gives us 127 * 90 = ~31 years worth of epochs before we have
79+ // to worry about a rollover.
80+ epoch := uint8 (now .Sub (time .Date (2024 , 01 , 01 , 00 , 00 , 00 , 00 , time .UTC )) / (90 * 24 * time .Hour ))
81+ if epoch & 0x80 != 0 {
82+ // If the first bit is a 1, either the current date is before the epoch
83+ // start date, or we've gone too far into the future. Error out before we
84+ // accidentally generate a negative ID.
85+ return 0 , fmt .Errorf ("invalid epoch: %d" , epoch )
86+ }
87+ idBytes [0 ] = epoch
88+
89+ id := binary .BigEndian .Uint64 (idBytes )
90+ return int64 (id ), nil
91+ }
92+
93+ // looksLikeRandomID returns true if the input ID looks like it might belong to
94+ // the new schema which uses epoch-prefixed random IDs instead of auto-increment
95+ // columns. This is only necessary during the migration period when we are
96+ // reading from both the old and new schemas simultaneously.
97+ func looksLikeRandomID (id int64 , now time.Time ) bool {
98+ // Compute the current and previous epochs. If the input ID starts with one of
99+ // those two epochs, it's one of ours. Otherwise, it came from somewhere
100+ // unknown and we should ask the old schema about it just in case.
101+ currEpoch := uint8 (now .Sub (time .Date (2024 , 01 , 01 , 00 , 00 , 00 , 00 , time .UTC )) / (90 * 24 * time .Hour ))
102+ prevEpoch := uint8 (now .Add (- 90 * 24 * time .Hour ).Sub (time .Date (2024 , 01 , 01 , 00 , 00 , 00 , 00 , time .UTC )) / (90 * 24 * time .Hour ))
103+
104+ buf := make ([]byte , 8 )
105+ binary .BigEndian .PutUint64 (buf , uint64 (id ))
106+ if buf [0 ] == currEpoch || buf [0 ] == prevEpoch {
107+ return true
108+ }
109+ return false
110+ }
111+
62112const regFields = "id, jwk, jwk_sha256, contact, agreement, createdAt, LockCol, status"
63113
64114// ClearEmail removes the provided email address from one specified registration. If
@@ -1409,3 +1459,47 @@ type pausedModel struct {
14091459 PausedAt time.Time `db:"pausedAt"`
14101460 UnpausedAt * time.Time `db:"unpausedAt"`
14111461}
1462+
1463+ // orders2Model represents a row in the "orders2" table.
1464+ type orders2Model struct {
1465+ ID int64
1466+ RegistrationID int64
1467+ Created time.Time
1468+ Expires time.Time
1469+ AuthorizationIDs []int64 // Actually a JSON list of ints
1470+ Profile string
1471+ BeganProcessing bool
1472+ Error []byte
1473+ CertificateSerial string
1474+ }
1475+
1476+ // authorizationsModel represents a row in the "authorizations" table.
1477+ type authorizationsModel struct {
1478+ ID int64
1479+ RegistrationID int64
1480+ IdentifierType uint8
1481+ IdentifierValue string
1482+ Created time.Time
1483+ Expires time.Time
1484+ Profile string
1485+ Challenges uint8
1486+ Token []byte
1487+ Status uint8
1488+ ValidationIDs []int64 // Actually a JSON list of ints
1489+ }
1490+
1491+ // validationsModel represents a row in the "validations" table.
1492+ type validationsModel struct {
1493+ ID int64
1494+ Challenge uint8
1495+ AttemptedAt time.Time
1496+ Status uint8
1497+ Record []byte
1498+ }
1499+
1500+ // authzReuseModel represents a row in the "authzReuse" table.
1501+ type authzReuseModel struct {
1502+ ID int64 `db:"accountID_identifier"`
1503+ AuthzID int64
1504+ Expires time.Time
1505+ }
0 commit comments