github.com/letsencrypt/boulder@v0.20251208.0/sa/model.go (about)

     1  package sa
     2  
     3  import (
     4  	"context"
     5  	"crypto/sha256"
     6  	"crypto/x509"
     7  	"database/sql"
     8  	"encoding/base64"
     9  	"encoding/json"
    10  	"errors"
    11  	"fmt"
    12  	"google.golang.org/protobuf/proto"
    13  	"math"
    14  	"net/netip"
    15  	"net/url"
    16  	"strconv"
    17  	"strings"
    18  	"time"
    19  
    20  	"github.com/go-jose/go-jose/v4"
    21  	"google.golang.org/protobuf/types/known/durationpb"
    22  	"google.golang.org/protobuf/types/known/timestamppb"
    23  
    24  	"github.com/letsencrypt/boulder/core"
    25  	corepb "github.com/letsencrypt/boulder/core/proto"
    26  	"github.com/letsencrypt/boulder/db"
    27  	berrors "github.com/letsencrypt/boulder/errors"
    28  	"github.com/letsencrypt/boulder/grpc"
    29  	"github.com/letsencrypt/boulder/identifier"
    30  	"github.com/letsencrypt/boulder/probs"
    31  	"github.com/letsencrypt/boulder/revocation"
    32  	sapb "github.com/letsencrypt/boulder/sa/proto"
    33  )
    34  
    35  // errBadJSON is an error type returned when a json.Unmarshal performed by the
    36  // SA fails. It includes both the Unmarshal error and the original JSON data in
    37  // its error message to make it easier to track down the bad JSON data.
    38  type errBadJSON struct {
    39  	msg  string
    40  	json []byte
    41  	err  error
    42  }
    43  
    44  // Error returns an error message that includes the json.Unmarshal error as well
    45  // as the bad JSON data.
    46  func (e errBadJSON) Error() string {
    47  	return fmt.Sprintf(
    48  		"%s: error unmarshaling JSON %q: %s",
    49  		e.msg,
    50  		string(e.json),
    51  		e.err)
    52  }
    53  
    54  // badJSONError is a convenience function for constructing a errBadJSON instance
    55  // with the provided args.
    56  func badJSONError(msg string, jsonData []byte, err error) error {
    57  	return errBadJSON{
    58  		msg:  msg,
    59  		json: jsonData,
    60  		err:  err,
    61  	}
    62  }
    63  
    64  const regFields = "id, jwk, jwk_sha256, agreement, createdAt, status"
    65  
    66  // selectRegistration selects all fields of one registration model
    67  func selectRegistration(ctx context.Context, s db.OneSelector, whereCol string, args ...any) (*regModel, error) {
    68  	if whereCol != "id" && whereCol != "jwk_sha256" {
    69  		return nil, fmt.Errorf("column name %q invalid for registrations table WHERE clause", whereCol)
    70  	}
    71  
    72  	var model regModel
    73  	err := s.SelectOne(
    74  		ctx,
    75  		&model,
    76  		"SELECT "+regFields+" FROM registrations WHERE "+whereCol+" = ? LIMIT 1",
    77  		args...,
    78  	)
    79  	return &model, err
    80  }
    81  
    82  const certFields = "id, registrationID, serial, digest, der, issued, expires"
    83  
    84  // SelectCertificate selects all fields of one certificate object identified by
    85  // a serial. If more than one row contains the same serial only the first is
    86  // returned.
    87  func SelectCertificate(ctx context.Context, s db.OneSelector, serial string) (*corepb.Certificate, error) {
    88  	var model certificateModel
    89  	err := s.SelectOne(
    90  		ctx,
    91  		&model,
    92  		"SELECT "+certFields+" FROM certificates WHERE serial = ? LIMIT 1",
    93  		serial,
    94  	)
    95  	return model.toPb(), err
    96  }
    97  
    98  const precertFields = "registrationID, serial, der, issued, expires"
    99  
   100  // SelectPrecertificate selects all fields of one precertificate object
   101  // identified by serial.
   102  func SelectPrecertificate(ctx context.Context, s db.OneSelector, serial string) (*corepb.Certificate, error) {
   103  	var model lintingCertModel
   104  	err := s.SelectOne(
   105  		ctx,
   106  		&model,
   107  		"SELECT "+precertFields+" FROM precertificates WHERE serial = ? LIMIT 1",
   108  		serial)
   109  	if err != nil {
   110  		return nil, err
   111  	}
   112  	return model.toPb(), nil
   113  }
   114  
   115  // SelectCertificates selects all fields of multiple certificate objects
   116  //
   117  // Returns a slice of *corepb.Certificate along with the highest ID field seen
   118  // (which can be used as input to a subsequent query when iterating in primary
   119  // key order).
   120  func SelectCertificates(ctx context.Context, s db.Selector, q string, args map[string]any) ([]*corepb.Certificate, int64, error) {
   121  	var models []certificateModel
   122  	_, err := s.Select(
   123  		ctx,
   124  		&models,
   125  		"SELECT "+certFields+" FROM certificates "+q, args)
   126  	var pbs []*corepb.Certificate
   127  	var highestID int64
   128  	for _, m := range models {
   129  		pbs = append(pbs, m.toPb())
   130  		if m.ID > highestID {
   131  			highestID = m.ID
   132  		}
   133  	}
   134  	return pbs, highestID, err
   135  }
   136  
   137  type CertStatusMetadata struct {
   138  	ID                    int64             `db:"id"`
   139  	Serial                string            `db:"serial"`
   140  	Status                core.OCSPStatus   `db:"status"`
   141  	OCSPLastUpdated       time.Time         `db:"ocspLastUpdated"`
   142  	RevokedDate           time.Time         `db:"revokedDate"`
   143  	RevokedReason         revocation.Reason `db:"revokedReason"`
   144  	LastExpirationNagSent time.Time         `db:"lastExpirationNagSent"`
   145  	NotAfter              time.Time         `db:"notAfter"`
   146  	IsExpired             bool              `db:"isExpired"`
   147  	IssuerID              int64             `db:"issuerID"`
   148  }
   149  
   150  const certStatusFields = "id, serial, status, ocspLastUpdated, revokedDate, revokedReason, lastExpirationNagSent, notAfter, isExpired, issuerID"
   151  
   152  // SelectCertificateStatus selects all fields of one certificate status model
   153  // identified by serial
   154  func SelectCertificateStatus(ctx context.Context, s db.OneSelector, serial string) (*corepb.CertificateStatus, error) {
   155  	var model certificateStatusModel
   156  	err := s.SelectOne(
   157  		ctx,
   158  		&model,
   159  		"SELECT "+certStatusFields+" FROM certificateStatus WHERE serial = ? LIMIT 1",
   160  		serial,
   161  	)
   162  	return model.toPb(), err
   163  }
   164  
   165  // RevocationStatusModel represents a small subset of the columns in the
   166  // certificateStatus table, used to determine the authoritative revocation
   167  // status of a certificate.
   168  type RevocationStatusModel struct {
   169  	Status        core.OCSPStatus   `db:"status"`
   170  	RevokedDate   time.Time         `db:"revokedDate"`
   171  	RevokedReason revocation.Reason `db:"revokedReason"`
   172  }
   173  
   174  // SelectRevocationStatus returns the authoritative revocation information for
   175  // the certificate with the given serial.
   176  func SelectRevocationStatus(ctx context.Context, s db.OneSelector, serial string) (*sapb.RevocationStatus, error) {
   177  	var model RevocationStatusModel
   178  	err := s.SelectOne(
   179  		ctx,
   180  		&model,
   181  		"SELECT status, revokedDate, revokedReason FROM certificateStatus WHERE serial = ? LIMIT 1",
   182  		serial,
   183  	)
   184  	if err != nil {
   185  		return nil, err
   186  	}
   187  
   188  	statusInt, ok := core.OCSPStatusToInt[model.Status]
   189  	if !ok {
   190  		return nil, fmt.Errorf("got unrecognized status %q", model.Status)
   191  	}
   192  
   193  	return &sapb.RevocationStatus{
   194  		Status:        int64(statusInt),
   195  		RevokedDate:   timestamppb.New(model.RevokedDate),
   196  		RevokedReason: int64(model.RevokedReason),
   197  	}, nil
   198  }
   199  
   200  var mediumBlobSize = int(math.Pow(2, 24))
   201  
   202  type issuedNameModel struct {
   203  	ID           int64     `db:"id"`
   204  	ReversedName string    `db:"reversedName"`
   205  	NotBefore    time.Time `db:"notBefore"`
   206  	Serial       string    `db:"serial"`
   207  }
   208  
   209  // regModel is the description of a core.Registration in the database before
   210  type regModel struct {
   211  	ID        int64     `db:"id"`
   212  	Key       []byte    `db:"jwk"`
   213  	KeySHA256 string    `db:"jwk_sha256"`
   214  	Agreement string    `db:"agreement"`
   215  	CreatedAt time.Time `db:"createdAt"`
   216  	Status    string    `db:"status"`
   217  }
   218  
   219  func registrationPbToModel(reg *corepb.Registration) (*regModel, error) {
   220  	// Even though we don't need to convert from JSON to an in-memory JSONWebKey
   221  	// for the sake of the `Key` field, we do need to do the conversion in order
   222  	// to compute the SHA256 key digest.
   223  	var jwk jose.JSONWebKey
   224  	err := jwk.UnmarshalJSON(reg.Key)
   225  	if err != nil {
   226  		return nil, err
   227  	}
   228  	sha, err := core.KeyDigestB64(jwk.Key)
   229  	if err != nil {
   230  		return nil, err
   231  	}
   232  
   233  	var createdAt time.Time
   234  	if !core.IsAnyNilOrZero(reg.CreatedAt) {
   235  		createdAt = reg.CreatedAt.AsTime()
   236  	}
   237  
   238  	return &regModel{
   239  		ID:        reg.Id,
   240  		Key:       reg.Key,
   241  		KeySHA256: sha,
   242  		Agreement: reg.Agreement,
   243  		CreatedAt: createdAt,
   244  		Status:    reg.Status,
   245  	}, nil
   246  }
   247  
   248  func registrationModelToPb(reg *regModel) (*corepb.Registration, error) {
   249  	if reg.ID == 0 || len(reg.Key) == 0 {
   250  		return nil, errors.New("incomplete Registration retrieved from DB")
   251  	}
   252  
   253  	return &corepb.Registration{
   254  		Id:        reg.ID,
   255  		Key:       reg.Key,
   256  		Agreement: reg.Agreement,
   257  		CreatedAt: timestamppb.New(reg.CreatedAt.UTC()),
   258  		Status:    reg.Status,
   259  	}, nil
   260  }
   261  
   262  type recordedSerialModel struct {
   263  	ID             int64
   264  	Serial         string
   265  	RegistrationID int64
   266  	Created        time.Time
   267  	Expires        time.Time
   268  }
   269  
   270  type lintingCertModel struct {
   271  	ID             int64
   272  	Serial         string
   273  	RegistrationID int64
   274  	DER            []byte
   275  	Issued         time.Time
   276  	Expires        time.Time
   277  }
   278  
   279  func (model lintingCertModel) toPb() *corepb.Certificate {
   280  	return &corepb.Certificate{
   281  		RegistrationID: model.RegistrationID,
   282  		Serial:         model.Serial,
   283  		Digest:         "",
   284  		Der:            model.DER,
   285  		Issued:         timestamppb.New(model.Issued),
   286  		Expires:        timestamppb.New(model.Expires),
   287  	}
   288  }
   289  
   290  type certificateModel struct {
   291  	ID             int64     `db:"id"`
   292  	RegistrationID int64     `db:"registrationID"`
   293  	Serial         string    `db:"serial"`
   294  	Digest         string    `db:"digest"`
   295  	DER            []byte    `db:"der"`
   296  	Issued         time.Time `db:"issued"`
   297  	Expires        time.Time `db:"expires"`
   298  }
   299  
   300  func (model certificateModel) toPb() *corepb.Certificate {
   301  	return &corepb.Certificate{
   302  		RegistrationID: model.RegistrationID,
   303  		Serial:         model.Serial,
   304  		Digest:         model.Digest,
   305  		Der:            model.DER,
   306  		Issued:         timestamppb.New(model.Issued),
   307  		Expires:        timestamppb.New(model.Expires),
   308  	}
   309  }
   310  
   311  type certificateStatusModel struct {
   312  	ID                    int64             `db:"id"`
   313  	Serial                string            `db:"serial"`
   314  	Status                core.OCSPStatus   `db:"status"`
   315  	OCSPLastUpdated       time.Time         `db:"ocspLastUpdated"`
   316  	RevokedDate           time.Time         `db:"revokedDate"`
   317  	RevokedReason         revocation.Reason `db:"revokedReason"`
   318  	LastExpirationNagSent time.Time         `db:"lastExpirationNagSent"`
   319  	NotAfter              time.Time         `db:"notAfter"`
   320  	IsExpired             bool              `db:"isExpired"`
   321  	IssuerID              int64             `db:"issuerID"`
   322  }
   323  
   324  func (model certificateStatusModel) toPb() *corepb.CertificateStatus {
   325  	return &corepb.CertificateStatus{
   326  		Serial:                model.Serial,
   327  		Status:                string(model.Status),
   328  		OcspLastUpdated:       timestamppb.New(model.OCSPLastUpdated),
   329  		RevokedDate:           timestamppb.New(model.RevokedDate),
   330  		RevokedReason:         int64(model.RevokedReason),
   331  		LastExpirationNagSent: timestamppb.New(model.LastExpirationNagSent),
   332  		NotAfter:              timestamppb.New(model.NotAfter),
   333  		IsExpired:             model.IsExpired,
   334  		IssuerID:              model.IssuerID,
   335  	}
   336  }
   337  
   338  // orderModel represents one row in the orders table. The CertificateProfileName
   339  // column is a pointer because the column is NULL-able.
   340  type orderModel struct {
   341  	ID                     int64
   342  	RegistrationID         int64
   343  	Expires                time.Time
   344  	Created                time.Time
   345  	Error                  []byte
   346  	CertificateSerial      string
   347  	BeganProcessing        bool
   348  	CertificateProfileName *string
   349  	Replaces               *string
   350  	Authzs                 []byte
   351  }
   352  
   353  type orderToAuthzModel struct {
   354  	OrderID int64
   355  	AuthzID int64
   356  }
   357  
   358  func modelToOrder(om *orderModel) (*corepb.Order, error) {
   359  	profile := ""
   360  	if om.CertificateProfileName != nil {
   361  		profile = *om.CertificateProfileName
   362  	}
   363  	replaces := ""
   364  	if om.Replaces != nil {
   365  		replaces = *om.Replaces
   366  	}
   367  	var v2Authorizations []int64
   368  	if len(om.Authzs) > 0 {
   369  		var decodedAuthzs sapb.Authzs
   370  		err := proto.Unmarshal(om.Authzs, &decodedAuthzs)
   371  		if err != nil {
   372  			return nil, err
   373  		}
   374  		v2Authorizations = decodedAuthzs.AuthzIDs
   375  	}
   376  	order := &corepb.Order{
   377  		Id:                     om.ID,
   378  		RegistrationID:         om.RegistrationID,
   379  		Expires:                timestamppb.New(om.Expires),
   380  		Created:                timestamppb.New(om.Created),
   381  		CertificateSerial:      om.CertificateSerial,
   382  		BeganProcessing:        om.BeganProcessing,
   383  		CertificateProfileName: profile,
   384  		Replaces:               replaces,
   385  		V2Authorizations:       v2Authorizations,
   386  	}
   387  	if len(om.Error) > 0 {
   388  		var problem corepb.ProblemDetails
   389  		err := json.Unmarshal(om.Error, &problem)
   390  		if err != nil {
   391  			return &corepb.Order{}, badJSONError(
   392  				"failed to unmarshal order model's error",
   393  				om.Error,
   394  				err)
   395  		}
   396  		order.Error = &problem
   397  	}
   398  	return order, nil
   399  }
   400  
   401  var challTypeToUint = map[string]uint8{
   402  	"http-01":        0,
   403  	"dns-01":         1,
   404  	"tls-alpn-01":    2,
   405  	"dns-account-01": 3,
   406  }
   407  
   408  var uintToChallType = map[uint8]string{
   409  	0: "http-01",
   410  	1: "dns-01",
   411  	2: "tls-alpn-01",
   412  	3: "dns-account-01",
   413  }
   414  
   415  var identifierTypeToUint = map[string]uint8{
   416  	"dns": 0,
   417  	"ip":  1,
   418  }
   419  
   420  var uintToIdentifierType = map[uint8]identifier.IdentifierType{
   421  	0: "dns",
   422  	1: "ip",
   423  }
   424  
   425  var statusToUint = map[core.AcmeStatus]uint8{
   426  	core.StatusPending:     0,
   427  	core.StatusValid:       1,
   428  	core.StatusInvalid:     2,
   429  	core.StatusDeactivated: 3,
   430  	core.StatusRevoked:     4,
   431  }
   432  
   433  var uintToStatus = map[uint8]core.AcmeStatus{
   434  	0: core.StatusPending,
   435  	1: core.StatusValid,
   436  	2: core.StatusInvalid,
   437  	3: core.StatusDeactivated,
   438  	4: core.StatusRevoked,
   439  }
   440  
   441  func statusUint(status core.AcmeStatus) uint8 {
   442  	return statusToUint[status]
   443  }
   444  
   445  // authzFields is used in a variety of places in sa.go, and modifications to
   446  // it must be carried through to every use in sa.go
   447  const authzFields = "id, identifierType, identifierValue, registrationID, certificateProfileName, status, expires, challenges, attempted, attemptedAt, token, validationError, validationRecord"
   448  
   449  // authzModel represents one row in the authz2 table. The CertificateProfileName
   450  // column is a pointer because the column is NULL-able.
   451  type authzModel struct {
   452  	ID                     int64      `db:"id"`
   453  	IdentifierType         uint8      `db:"identifierType"`
   454  	IdentifierValue        string     `db:"identifierValue"`
   455  	RegistrationID         int64      `db:"registrationID"`
   456  	CertificateProfileName *string    `db:"certificateProfileName"`
   457  	Status                 uint8      `db:"status"`
   458  	Expires                time.Time  `db:"expires"`
   459  	Challenges             uint8      `db:"challenges"`
   460  	Attempted              *uint8     `db:"attempted"`
   461  	AttemptedAt            *time.Time `db:"attemptedAt"`
   462  	Token                  []byte     `db:"token"`
   463  	ValidationError        []byte     `db:"validationError"`
   464  	ValidationRecord       []byte     `db:"validationRecord"`
   465  }
   466  
   467  // rehydrateHostPort mutates a validation record. If the URL in the validation
   468  // record cannot be parsed, an error will be returned. If the Hostname and Port
   469  // fields already exist in the validation record, they will be retained.
   470  // Otherwise, the Hostname and Port will be derived and set from the URL field
   471  // of the validation record.
   472  func rehydrateHostPort(vr *core.ValidationRecord) error {
   473  	if vr.URL == "" {
   474  		return fmt.Errorf("rehydrating validation record, URL field cannot be empty")
   475  	}
   476  
   477  	parsedUrl, err := url.Parse(vr.URL)
   478  	if err != nil {
   479  		return fmt.Errorf("parsing validation record URL %q: %w", vr.URL, err)
   480  	}
   481  
   482  	if vr.Hostname == "" {
   483  		hostname := parsedUrl.Hostname()
   484  		if hostname == "" {
   485  			return fmt.Errorf("hostname missing in URL %q", vr.URL)
   486  		}
   487  		vr.Hostname = hostname
   488  	}
   489  
   490  	if vr.Port == "" {
   491  		// CABF BRs section 1.6.1: Authorized Ports: One of the following ports: 80
   492  		// (http), 443 (https)
   493  		if parsedUrl.Port() == "" {
   494  			// If there is only a scheme, then we'll determine the appropriate port.
   495  			switch parsedUrl.Scheme {
   496  			case "https":
   497  				vr.Port = "443"
   498  			case "http":
   499  				vr.Port = "80"
   500  			default:
   501  				// This should never happen since the VA should have already
   502  				// checked the scheme.
   503  				return fmt.Errorf("unknown scheme %q in URL %q", parsedUrl.Scheme, vr.URL)
   504  			}
   505  		} else if parsedUrl.Port() == "80" || parsedUrl.Port() == "443" {
   506  			// If :80 or :443 were embedded in the URL field
   507  			// e.g. '"url":"https://example.com:443"'
   508  			vr.Port = parsedUrl.Port()
   509  		} else {
   510  			return fmt.Errorf("only ports 80/tcp and 443/tcp are allowed in URL %q", vr.URL)
   511  		}
   512  	}
   513  
   514  	return nil
   515  }
   516  
   517  // SelectAuthzsMatchingIssuance looks for a set of authzs that would have
   518  // authorized a given issuance that is known to have occurred. The returned
   519  // authzs will all belong to the given regID, will have potentially been valid
   520  // at the time of issuance, and will have the appropriate identifier type and
   521  // value. This may return multiple authzs for the same identifier type and value.
   522  //
   523  // This returns "potentially" valid authzs because a client may have set an
   524  // authzs status to deactivated after issuance, so we return both valid and
   525  // deactivated authzs. It also uses a small amount of leeway (1s) to account
   526  // for possible clock skew.
   527  //
   528  // This function doesn't do anything special for authzs with an expiration in
   529  // the past. If the stored authz has a valid status, it is returned with a
   530  // valid status regardless of whether it is also expired.
   531  func SelectAuthzsMatchingIssuance(
   532  	ctx context.Context,
   533  	s db.Selector,
   534  	regID int64,
   535  	issued time.Time,
   536  	idents identifier.ACMEIdentifiers,
   537  ) ([]*corepb.Authorization, error) {
   538  	// The WHERE clause returned by this function does not contain any
   539  	// user-controlled strings; all user-controlled input ends up in the
   540  	// returned placeholder args.
   541  	identConditions, identArgs := buildIdentifierQueryConditions(idents)
   542  	query := fmt.Sprintf(`SELECT %s FROM authz2 WHERE
   543  			registrationID = ? AND
   544  			status IN (?, ?) AND
   545  			expires >= ? AND
   546  			attemptedAt <= ? AND
   547  			(%s)`,
   548  		authzFields,
   549  		identConditions)
   550  	var args []any
   551  	args = append(args,
   552  		regID,
   553  		statusToUint[core.StatusValid], statusToUint[core.StatusDeactivated],
   554  		issued.Add(-1*time.Second), // leeway for clock skew
   555  		issued.Add(1*time.Second),  // leeway for clock skew
   556  	)
   557  	args = append(args, identArgs...)
   558  
   559  	var authzModels []authzModel
   560  	_, err := s.Select(ctx, &authzModels, query, args...)
   561  	if err != nil {
   562  		return nil, err
   563  	}
   564  
   565  	var authzs []*corepb.Authorization
   566  	for _, model := range authzModels {
   567  		authz, err := modelToAuthzPB(model)
   568  		if err != nil {
   569  			return nil, err
   570  		}
   571  		authzs = append(authzs, authz)
   572  
   573  	}
   574  	return authzs, err
   575  }
   576  
   577  // hasMultipleNonPendingChallenges checks if a slice of challenges contains
   578  // more than one non-pending challenge
   579  func hasMultipleNonPendingChallenges(challenges []*corepb.Challenge) bool {
   580  	nonPending := false
   581  	for _, c := range challenges {
   582  		if c.Status == string(core.StatusValid) || c.Status == string(core.StatusInvalid) {
   583  			if !nonPending {
   584  				nonPending = true
   585  			} else {
   586  				return true
   587  			}
   588  		}
   589  	}
   590  	return false
   591  }
   592  
   593  // newAuthzReqToModel converts an sapb.NewAuthzRequest to the authzModel storage
   594  // representation. It hardcodes the status to "pending" because it should be
   595  // impossible to create an authz in any other state.
   596  func newAuthzReqToModel(authz *sapb.NewAuthzRequest, profile string) (*authzModel, error) {
   597  	am := &authzModel{
   598  		IdentifierType:  identifierTypeToUint[authz.Identifier.Type],
   599  		IdentifierValue: authz.Identifier.Value,
   600  		RegistrationID:  authz.RegistrationID,
   601  		Status:          statusToUint[core.StatusPending],
   602  		Expires:         authz.Expires.AsTime(),
   603  	}
   604  
   605  	if profile != "" {
   606  		am.CertificateProfileName = &profile
   607  	}
   608  
   609  	for _, challType := range authz.ChallengeTypes {
   610  		// Set the challenge type bit in the bitmap
   611  		am.Challenges |= 1 << challTypeToUint[challType]
   612  	}
   613  
   614  	token, err := base64.RawURLEncoding.DecodeString(authz.Token)
   615  	if err != nil {
   616  		return nil, err
   617  	}
   618  	am.Token = token
   619  
   620  	return am, nil
   621  }
   622  
   623  // authzPBToModel converts a protobuf authorization representation to the
   624  // authzModel storage representation.
   625  // Deprecated: this function is only used as part of test setup, do not
   626  // introduce any new uses in production code.
   627  func authzPBToModel(authz *corepb.Authorization) (*authzModel, error) {
   628  	ident := identifier.FromProto(authz.Identifier)
   629  
   630  	am := &authzModel{
   631  		IdentifierType:  identifierTypeToUint[ident.ToProto().Type],
   632  		IdentifierValue: ident.Value,
   633  		RegistrationID:  authz.RegistrationID,
   634  		Status:          statusToUint[core.AcmeStatus(authz.Status)],
   635  		Expires:         authz.Expires.AsTime(),
   636  	}
   637  	if authz.CertificateProfileName != "" {
   638  		profile := authz.CertificateProfileName
   639  		am.CertificateProfileName = &profile
   640  	}
   641  	if authz.Id != "" {
   642  		// The v1 internal authorization objects use a string for the ID, the v2
   643  		// storage format uses a integer ID. In order to maintain compatibility we
   644  		// convert the integer ID to a string.
   645  		id, err := strconv.Atoi(authz.Id)
   646  		if err != nil {
   647  			return nil, err
   648  		}
   649  		am.ID = int64(id)
   650  	}
   651  	if hasMultipleNonPendingChallenges(authz.Challenges) {
   652  		return nil, errors.New("multiple challenges are non-pending")
   653  	}
   654  	// In the v2 authorization style we don't store individual challenges with their own
   655  	// token, validation errors/records, etc. Instead we store a single token/error/record
   656  	// set, a bitmap of available challenge types, and a row indicating which challenge type
   657  	// was 'attempted'.
   658  	//
   659  	// Since we don't currently have the singular token/error/record set abstracted out to
   660  	// the core authorization type yet we need to extract these from the challenges array.
   661  	// We assume that the token in each challenge is the same and that if any of the challenges
   662  	// has a non-pending status that it should be considered the 'attempted' challenge and
   663  	// we extract the error/record set from that particular challenge.
   664  	var tokenStr string
   665  	for _, chall := range authz.Challenges {
   666  		// Set the challenge type bit in the bitmap
   667  		am.Challenges |= 1 << challTypeToUint[chall.Type]
   668  		tokenStr = chall.Token
   669  		// If the challenge status is not core.StatusPending we assume it was the 'attempted'
   670  		// challenge and extract the relevant fields we need.
   671  		if chall.Status == string(core.StatusValid) || chall.Status == string(core.StatusInvalid) {
   672  			attemptedType := challTypeToUint[chall.Type]
   673  			am.Attempted = &attemptedType
   674  
   675  			// If validated Unix timestamp is zero then keep the core.Challenge Validated object nil.
   676  			var validated *time.Time
   677  			if !core.IsAnyNilOrZero(chall.Validated) {
   678  				val := chall.Validated.AsTime()
   679  				validated = &val
   680  			}
   681  			am.AttemptedAt = validated
   682  
   683  			// Marshal corepb.ValidationRecords to core.ValidationRecords so that we
   684  			// can marshal them to JSON.
   685  			records := make([]core.ValidationRecord, len(chall.Validationrecords))
   686  			for i, recordPB := range chall.Validationrecords {
   687  				if chall.Type == string(core.ChallengeTypeHTTP01) {
   688  					// Remove these fields because they can be rehydrated later
   689  					// on from the URL field.
   690  					recordPB.Hostname = ""
   691  					recordPB.Port = ""
   692  				}
   693  				var err error
   694  				records[i], err = grpc.PBToValidationRecord(recordPB)
   695  				if err != nil {
   696  					return nil, err
   697  				}
   698  			}
   699  			var err error
   700  			am.ValidationRecord, err = json.Marshal(records)
   701  			if err != nil {
   702  				return nil, err
   703  			}
   704  			// If there is a error associated with the challenge marshal it to JSON
   705  			// so that we can store it in the database.
   706  			if chall.Error != nil {
   707  				prob, err := grpc.PBToProblemDetails(chall.Error)
   708  				if err != nil {
   709  					return nil, err
   710  				}
   711  				am.ValidationError, err = json.Marshal(prob)
   712  				if err != nil {
   713  					return nil, err
   714  				}
   715  			}
   716  		}
   717  		token, err := base64.RawURLEncoding.DecodeString(tokenStr)
   718  		if err != nil {
   719  			return nil, err
   720  		}
   721  		am.Token = token
   722  	}
   723  
   724  	return am, nil
   725  }
   726  
   727  // populateAttemptedFields takes a challenge and populates it with the validation fields status,
   728  // validation records, and error (the latter only if the validation failed) from an authzModel.
   729  func populateAttemptedFields(am authzModel, challenge *corepb.Challenge) error {
   730  	if len(am.ValidationError) != 0 {
   731  		// If the error is non-empty the challenge must be invalid.
   732  		challenge.Status = string(core.StatusInvalid)
   733  		var prob probs.ProblemDetails
   734  		err := json.Unmarshal(am.ValidationError, &prob)
   735  		if err != nil {
   736  			return badJSONError(
   737  				"failed to unmarshal authz2 model's validation error",
   738  				am.ValidationError,
   739  				err)
   740  		}
   741  		challenge.Error, err = grpc.ProblemDetailsToPB(&prob)
   742  		if err != nil {
   743  			return err
   744  		}
   745  	} else {
   746  		// If the error is empty the challenge must be valid.
   747  		challenge.Status = string(core.StatusValid)
   748  	}
   749  	var records []core.ValidationRecord
   750  	err := json.Unmarshal(am.ValidationRecord, &records)
   751  	if err != nil {
   752  		return badJSONError(
   753  			"failed to unmarshal authz2 model's validation record",
   754  			am.ValidationRecord,
   755  			err)
   756  	}
   757  	challenge.Validationrecords = make([]*corepb.ValidationRecord, len(records))
   758  	for i, r := range records {
   759  		// Fixes implicit memory aliasing in for loop so we can deference r
   760  		// later on for rehydrateHostPort.
   761  		if challenge.Type == string(core.ChallengeTypeHTTP01) {
   762  			err := rehydrateHostPort(&r)
   763  			if err != nil {
   764  				return err
   765  			}
   766  		}
   767  		challenge.Validationrecords[i], err = grpc.ValidationRecordToPB(r)
   768  		if err != nil {
   769  			return err
   770  		}
   771  	}
   772  	return nil
   773  }
   774  
   775  func modelToAuthzPB(am authzModel) (*corepb.Authorization, error) {
   776  	identType, ok := uintToIdentifierType[am.IdentifierType]
   777  	if !ok {
   778  		return nil, fmt.Errorf("unrecognized identifier type encoding %d", am.IdentifierType)
   779  	}
   780  
   781  	profile := ""
   782  	if am.CertificateProfileName != nil {
   783  		profile = *am.CertificateProfileName
   784  	}
   785  
   786  	pb := &corepb.Authorization{
   787  		Id:                     fmt.Sprintf("%d", am.ID),
   788  		Status:                 string(uintToStatus[am.Status]),
   789  		Identifier:             identifier.ACMEIdentifier{Type: identType, Value: am.IdentifierValue}.ToProto(),
   790  		RegistrationID:         am.RegistrationID,
   791  		Expires:                timestamppb.New(am.Expires),
   792  		CertificateProfileName: profile,
   793  	}
   794  	// Populate authorization challenge array. We do this by iterating through
   795  	// the challenge type bitmap and creating a challenge of each type if its
   796  	// bit is set. Each of these challenges has the token from the authorization
   797  	// model and has its status set to core.StatusPending by default. If the
   798  	// challenge type is equal to that in the 'attempted' row we set the status
   799  	// to core.StatusValid or core.StatusInvalid depending on if there is anything
   800  	// in ValidationError and populate the ValidationRecord and ValidationError
   801  	// fields.
   802  	for pos := range uint8(8) {
   803  		if (am.Challenges>>pos)&1 == 1 {
   804  			challType := uintToChallType[pos]
   805  			challenge := &corepb.Challenge{
   806  				Type:   challType,
   807  				Status: string(core.StatusPending),
   808  				Token:  base64.RawURLEncoding.EncodeToString(am.Token),
   809  			}
   810  			// If the challenge type matches the attempted type it must be either
   811  			// valid or invalid and we need to populate extra fields.
   812  			// Also, once any challenge has been attempted, we consider the other
   813  			// challenges "gone" per https://tools.ietf.org/html/rfc8555#section-7.1.4
   814  			if am.Attempted != nil {
   815  				if uintToChallType[*am.Attempted] == challType {
   816  					err := populateAttemptedFields(am, challenge)
   817  					if err != nil {
   818  						return nil, err
   819  					}
   820  					// Get the attemptedAt time and assign to the challenge validated time.
   821  					var validated *timestamppb.Timestamp
   822  					if am.AttemptedAt != nil {
   823  						validated = timestamppb.New(*am.AttemptedAt)
   824  					}
   825  					challenge.Validated = validated
   826  					pb.Challenges = append(pb.Challenges, challenge)
   827  				}
   828  			} else {
   829  				// When no challenge has been attempted yet, all challenges are still
   830  				// present.
   831  				pb.Challenges = append(pb.Challenges, challenge)
   832  			}
   833  		}
   834  	}
   835  	return pb, nil
   836  }
   837  
   838  type keyHashModel struct {
   839  	ID           int64
   840  	KeyHash      []byte
   841  	CertNotAfter time.Time
   842  	CertSerial   string
   843  }
   844  
   845  var stringToSourceInt = map[string]int{
   846  	"API":           1,
   847  	"admin-revoker": 2,
   848  }
   849  
   850  // incidentModel represents a row in the 'incidents' table.
   851  type incidentModel struct {
   852  	ID          int64     `db:"id"`
   853  	SerialTable string    `db:"serialTable"`
   854  	URL         string    `db:"url"`
   855  	RenewBy     time.Time `db:"renewBy"`
   856  	Enabled     bool      `db:"enabled"`
   857  }
   858  
   859  func incidentModelToPB(i incidentModel) sapb.Incident {
   860  	return sapb.Incident{
   861  		Id:          i.ID,
   862  		SerialTable: i.SerialTable,
   863  		Url:         i.URL,
   864  		RenewBy:     timestamppb.New(i.RenewBy),
   865  		Enabled:     i.Enabled,
   866  	}
   867  }
   868  
   869  // incidentSerialModel represents a row in an 'incident_*' table.
   870  type incidentSerialModel struct {
   871  	Serial         string     `db:"serial"`
   872  	RegistrationID *int64     `db:"registrationID"`
   873  	OrderID        *int64     `db:"orderID"`
   874  	LastNoticeSent *time.Time `db:"lastNoticeSent"`
   875  }
   876  
   877  // crlEntryModel has just the certificate status fields necessary to construct
   878  // an entry in a CRL.
   879  type crlEntryModel struct {
   880  	Serial        string            `db:"serial"`
   881  	Status        core.OCSPStatus   `db:"status"`
   882  	RevokedReason revocation.Reason `db:"revokedReason"`
   883  	RevokedDate   time.Time         `db:"revokedDate"`
   884  }
   885  
   886  // fqdnSet contains the SHA256 hash of the lowercased, comma joined dNSNames
   887  // contained in a certificate.
   888  type fqdnSet struct {
   889  	ID      int64
   890  	SetHash []byte
   891  	Serial  string
   892  	Issued  time.Time
   893  	Expires time.Time
   894  }
   895  
   896  // orderFQDNSet contains the SHA256 hash of the lowercased, comma joined names
   897  // from a new-order request, along with the corresponding orderID, the
   898  // registration ID, and the order expiry. This is used to find
   899  // existing orders for reuse.
   900  type orderFQDNSet struct {
   901  	ID             int64
   902  	SetHash        []byte
   903  	OrderID        int64
   904  	RegistrationID int64
   905  	Expires        time.Time
   906  }
   907  
   908  func addFQDNSet(ctx context.Context, db db.Inserter, idents identifier.ACMEIdentifiers, serial string, issued time.Time, expires time.Time) error {
   909  	return db.Insert(ctx, &fqdnSet{
   910  		SetHash: core.HashIdentifiers(idents),
   911  		Serial:  serial,
   912  		Issued:  issued,
   913  		Expires: expires,
   914  	})
   915  }
   916  
   917  // addOrderFQDNSet creates a new OrderFQDNSet row using the provided
   918  // information. This function accepts a transaction so that the orderFqdnSet
   919  // addition can take place within the order addition transaction. The caller is
   920  // required to rollback the transaction if an error is returned.
   921  func addOrderFQDNSet(
   922  	ctx context.Context,
   923  	db db.Inserter,
   924  	idents identifier.ACMEIdentifiers,
   925  	orderID int64,
   926  	regID int64,
   927  	expires time.Time) error {
   928  	return db.Insert(ctx, &orderFQDNSet{
   929  		SetHash:        core.HashIdentifiers(idents),
   930  		OrderID:        orderID,
   931  		RegistrationID: regID,
   932  		Expires:        expires,
   933  	})
   934  }
   935  
   936  // deleteOrderFQDNSet deletes a OrderFQDNSet row that matches the provided
   937  // orderID. This function accepts a transaction so that the deletion can
   938  // take place within the finalization transaction. The caller is required to
   939  // rollback the transaction if an error is returned.
   940  func deleteOrderFQDNSet(
   941  	ctx context.Context,
   942  	db db.Execer,
   943  	orderID int64) error {
   944  
   945  	result, err := db.ExecContext(ctx, `
   946  	  DELETE FROM orderFqdnSets
   947  		WHERE orderID = ?`,
   948  		orderID)
   949  	if err != nil {
   950  		return err
   951  	}
   952  	rowsDeleted, err := result.RowsAffected()
   953  	if err != nil {
   954  		return err
   955  	}
   956  	// We always expect there to be an order FQDN set row for each
   957  	// pending/processing order that is being finalized. If there isn't one then
   958  	// something is amiss and should be raised as an internal server error
   959  	if rowsDeleted == 0 {
   960  		return berrors.InternalServerError("No orderFQDNSet exists to delete")
   961  	}
   962  	return nil
   963  }
   964  
   965  func addIssuedNames(ctx context.Context, queryer db.Execer, cert *x509.Certificate, isRenewal bool) error {
   966  	if len(cert.DNSNames) == 0 && len(cert.IPAddresses) == 0 {
   967  		return berrors.InternalServerError("certificate has no DNSNames or IPAddresses")
   968  	}
   969  
   970  	multiInserter, err := db.NewMultiInserter("issuedNames", []string{"reversedName", "serial", "notBefore", "renewal"})
   971  	if err != nil {
   972  		return err
   973  	}
   974  	for _, name := range cert.DNSNames {
   975  		err = multiInserter.Add([]any{
   976  			reverseFQDN(name),
   977  			core.SerialToString(cert.SerialNumber),
   978  			cert.NotBefore.Truncate(24 * time.Hour),
   979  			isRenewal,
   980  		})
   981  		if err != nil {
   982  			return err
   983  		}
   984  	}
   985  	for _, ip := range cert.IPAddresses {
   986  		err = multiInserter.Add([]any{
   987  			ip.String(),
   988  			core.SerialToString(cert.SerialNumber),
   989  			cert.NotBefore.Truncate(24 * time.Hour),
   990  			isRenewal,
   991  		})
   992  		if err != nil {
   993  			return err
   994  		}
   995  	}
   996  	return multiInserter.Insert(ctx, queryer)
   997  }
   998  
   999  // EncodeIssuedName translates a FQDN to/from the issuedNames table by reversing
  1000  // its dot-separated elements, and translates an IP address by returning its
  1001  // normal string form.
  1002  //
  1003  // This is for strings of ambiguous identifier values. If you know your string
  1004  // is a FQDN, use reverseFQDN(). If you have an IP address, use
  1005  // netip.Addr.String() or net.IP.String().
  1006  func EncodeIssuedName(name string) string {
  1007  	netIP, err := netip.ParseAddr(name)
  1008  	if err == nil {
  1009  		return netIP.String()
  1010  	}
  1011  	return reverseFQDN(name)
  1012  }
  1013  
  1014  // reverseFQDN reverses the elements of a dot-separated FQDN.
  1015  //
  1016  // If your string might be an IP address, use EncodeIssuedName() instead.
  1017  func reverseFQDN(fqdn string) string {
  1018  	labels := strings.Split(fqdn, ".")
  1019  	for i, j := 0, len(labels)-1; i < j; i, j = i+1, j-1 {
  1020  		labels[i], labels[j] = labels[j], labels[i]
  1021  	}
  1022  	return strings.Join(labels, ".")
  1023  }
  1024  
  1025  func addKeyHash(ctx context.Context, db db.Inserter, cert *x509.Certificate) error {
  1026  	if cert.RawSubjectPublicKeyInfo == nil {
  1027  		return errors.New("certificate has a nil RawSubjectPublicKeyInfo")
  1028  	}
  1029  	h := sha256.Sum256(cert.RawSubjectPublicKeyInfo)
  1030  	khm := &keyHashModel{
  1031  		KeyHash:      h[:],
  1032  		CertNotAfter: cert.NotAfter,
  1033  		CertSerial:   core.SerialToString(cert.SerialNumber),
  1034  	}
  1035  	return db.Insert(ctx, khm)
  1036  }
  1037  
  1038  var blockedKeysColumns = "keyHash, added, source, comment"
  1039  
  1040  // statusForOrder examines the status of a provided order's authorizations to
  1041  // determine what the overall status of the order should be. In summary:
  1042  //   - If the order has an error, the order is invalid
  1043  //   - If any of the order's authorizations are in any state other than
  1044  //     valid or pending, the order is invalid.
  1045  //   - If any of the order's authorizations are pending, the order is pending.
  1046  //   - If all of the order's authorizations are valid, and there is
  1047  //     a certificate serial, the order is valid.
  1048  //   - If all of the order's authorizations are valid, and we have began
  1049  //     processing, but there is no certificate serial, the order is processing.
  1050  //   - If all of the order's authorizations are valid, and we haven't begun
  1051  //     processing, then the order is status ready.
  1052  //
  1053  // An error is returned for any other case.
  1054  func statusForOrder(order *corepb.Order, authzValidityInfo []authzValidity, now time.Time) (string, error) {
  1055  	// Without any further work we know an order with an error is invalid
  1056  	if order.Error != nil {
  1057  		return string(core.StatusInvalid), nil
  1058  	}
  1059  
  1060  	// If the order is expired the status is invalid and we don't need to get
  1061  	// order authorizations. Its important to exit early in this case because an
  1062  	// order that references an expired authorization will be itself have been
  1063  	// expired (because we match the order expiry to the associated authz expiries
  1064  	// in ra.NewOrder), and expired authorizations may be purged from the DB.
  1065  	// Because of this purging fetching the authz's for an expired order may
  1066  	// return fewer authz objects than expected, triggering a 500 error response.
  1067  	if order.Expires.AsTime().Before(now) {
  1068  		return string(core.StatusInvalid), nil
  1069  	}
  1070  
  1071  	// If getAuthorizationStatuses returned a different number of authorization
  1072  	// objects than the order's slice of authorization IDs something has gone
  1073  	// wrong worth raising an internal error about.
  1074  	if len(authzValidityInfo) != len(order.V2Authorizations) {
  1075  		return "", berrors.InternalServerError(
  1076  			"getAuthorizationStatuses returned the wrong number of authorization statuses "+
  1077  				"(%d vs expected %d) for order %d",
  1078  			len(authzValidityInfo), len(order.V2Authorizations), order.Id)
  1079  	}
  1080  
  1081  	// Keep a count of the authorizations seen
  1082  	pendingAuthzs := 0
  1083  	validAuthzs := 0
  1084  	otherAuthzs := 0
  1085  	expiredAuthzs := 0
  1086  
  1087  	// Loop over each of the order's authorization objects to examine the authz status
  1088  	for _, info := range authzValidityInfo {
  1089  		switch uintToStatus[info.Status] {
  1090  		case core.StatusPending:
  1091  			pendingAuthzs++
  1092  		case core.StatusValid:
  1093  			validAuthzs++
  1094  		case core.StatusInvalid:
  1095  			otherAuthzs++
  1096  		case core.StatusDeactivated:
  1097  			otherAuthzs++
  1098  		case core.StatusRevoked:
  1099  			otherAuthzs++
  1100  		default:
  1101  			return "", berrors.InternalServerError(
  1102  				"Order is in an invalid state. Authz has invalid status %d",
  1103  				info.Status)
  1104  		}
  1105  		if info.Expires.Before(now) {
  1106  			expiredAuthzs++
  1107  		}
  1108  	}
  1109  
  1110  	// An order is invalid if **any** of its authzs are invalid, deactivated,
  1111  	// revoked, or expired, see https://tools.ietf.org/html/rfc8555#section-7.1.6
  1112  	if otherAuthzs > 0 || expiredAuthzs > 0 {
  1113  		return string(core.StatusInvalid), nil
  1114  	}
  1115  	// An order is pending if **any** of its authzs are pending
  1116  	if pendingAuthzs > 0 {
  1117  		return string(core.StatusPending), nil
  1118  	}
  1119  
  1120  	// An order is fully authorized if it has valid authzs for each of the order
  1121  	// identifiers
  1122  	fullyAuthorized := len(order.Identifiers) == validAuthzs
  1123  
  1124  	// If the order isn't fully authorized we've encountered an internal error:
  1125  	// Above we checked for any invalid or pending authzs and should have returned
  1126  	// early. Somehow we made it this far but also don't have the correct number
  1127  	// of valid authzs.
  1128  	if !fullyAuthorized {
  1129  		return "", berrors.InternalServerError(
  1130  			"Order has the incorrect number of valid authorizations & no pending, " +
  1131  				"deactivated or invalid authorizations")
  1132  	}
  1133  
  1134  	// If the order is fully authorized and the certificate serial is set then the
  1135  	// order is valid
  1136  	if fullyAuthorized && order.CertificateSerial != "" {
  1137  		return string(core.StatusValid), nil
  1138  	}
  1139  
  1140  	// If the order is fully authorized, and we have began processing it, then the
  1141  	// order is processing.
  1142  	if fullyAuthorized && order.BeganProcessing {
  1143  		return string(core.StatusProcessing), nil
  1144  	}
  1145  
  1146  	if fullyAuthorized && !order.BeganProcessing {
  1147  		return string(core.StatusReady), nil
  1148  	}
  1149  
  1150  	return "", berrors.InternalServerError(
  1151  		"Order %d is in an invalid state. No state known for this order's "+
  1152  			"authorizations", order.Id)
  1153  }
  1154  
  1155  // authzValidity is a subset of authzModel
  1156  type authzValidity struct {
  1157  	IdentifierType  uint8     `db:"identifierType"`
  1158  	IdentifierValue string    `db:"identifierValue"`
  1159  	Status          uint8     `db:"status"`
  1160  	Expires         time.Time `db:"expires"`
  1161  }
  1162  
  1163  // getAuthorizationStatuses takes a sequence of authz IDs, and returns the
  1164  // status and expiration date of each of them.
  1165  func getAuthorizationStatuses(ctx context.Context, s db.Selector, ids []int64) ([]authzValidity, error) {
  1166  	var params []any
  1167  	for _, id := range ids {
  1168  		params = append(params, id)
  1169  	}
  1170  	var validities []authzValidity
  1171  	_, err := s.Select(
  1172  		ctx,
  1173  		&validities,
  1174  		fmt.Sprintf("SELECT identifierType, identifierValue, status, expires FROM authz2 WHERE id IN (%s)",
  1175  			db.QuestionMarks(len(ids))),
  1176  		params...,
  1177  	)
  1178  	if err != nil {
  1179  		return nil, err
  1180  	}
  1181  
  1182  	return validities, nil
  1183  }
  1184  
  1185  // authzForOrder retrieves the authorization IDs for an order.
  1186  func authzForOrder(ctx context.Context, s db.Selector, orderID int64) ([]int64, error) {
  1187  	var v2IDs []int64
  1188  	_, err := s.Select(
  1189  		ctx,
  1190  		&v2IDs,
  1191  		"SELECT authzID FROM orderToAuthz2 WHERE orderID = ?",
  1192  		orderID,
  1193  	)
  1194  	return v2IDs, err
  1195  }
  1196  
  1197  // crlShardModel represents one row in the crlShards table. The ThisUpdate and
  1198  // NextUpdate fields are pointers because they are NULL-able columns.
  1199  type crlShardModel struct {
  1200  	ID          int64      `db:"id"`
  1201  	IssuerID    int64      `db:"issuerID"`
  1202  	Idx         int        `db:"idx"`
  1203  	ThisUpdate  *time.Time `db:"thisUpdate"`
  1204  	NextUpdate  *time.Time `db:"nextUpdate"`
  1205  	LeasedUntil time.Time  `db:"leasedUntil"`
  1206  }
  1207  
  1208  // revokedCertModel represents one row in the revokedCertificates table. It
  1209  // contains all of the information necessary to populate a CRL entry or OCSP
  1210  // response for the indicated certificate.
  1211  type revokedCertModel struct {
  1212  	ID            int64             `db:"id"`
  1213  	IssuerID      int64             `db:"issuerID"`
  1214  	Serial        string            `db:"serial"`
  1215  	NotAfterHour  time.Time         `db:"notAfterHour"`
  1216  	ShardIdx      int64             `db:"shardIdx"`
  1217  	RevokedDate   time.Time         `db:"revokedDate"`
  1218  	RevokedReason revocation.Reason `db:"revokedReason"`
  1219  }
  1220  
  1221  // replacementOrderModel represents one row in the replacementOrders table. It
  1222  // contains all of the information necessary to link a renewal order to the
  1223  // certificate it replaces.
  1224  type replacementOrderModel struct {
  1225  	// ID is an auto-incrementing row ID.
  1226  	ID int64 `db:"id"`
  1227  	// Serial is the serial number of the replaced certificate.
  1228  	Serial string `db:"serial"`
  1229  	// OrderId is the ID of the replacement order
  1230  	OrderID int64 `db:"orderID"`
  1231  	// OrderExpiry is the expiry time of the new order. This is used to
  1232  	// determine if we can accept a new replacement order for the same Serial.
  1233  	OrderExpires time.Time `db:"orderExpires"`
  1234  	// Replaced is a boolean indicating whether the certificate has been
  1235  	// replaced, i.e. whether the new order has been finalized. Once this is
  1236  	// true, no new replacement orders can be accepted for the same Serial.
  1237  	Replaced bool `db:"replaced"`
  1238  }
  1239  
  1240  // addReplacementOrder inserts or updates the replacementOrders row matching the
  1241  // provided serial with the details provided. This function accepts a
  1242  // transaction so that the insert or update takes place within the new order
  1243  // transaction.
  1244  func addReplacementOrder(ctx context.Context, db db.SelectExecer, serial string, orderID int64, orderExpires time.Time) error {
  1245  	var existingID []int64
  1246  	_, err := db.Select(ctx, &existingID, `
  1247  		SELECT id
  1248  		FROM replacementOrders
  1249  		WHERE serial = ?
  1250  		LIMIT 1`,
  1251  		serial,
  1252  	)
  1253  	if err != nil && !errors.Is(err, sql.ErrNoRows) {
  1254  		return fmt.Errorf("checking for existing replacement order: %w", err)
  1255  	}
  1256  
  1257  	if len(existingID) > 0 {
  1258  		// Update existing replacementOrder row.
  1259  		_, err = db.ExecContext(ctx, `
  1260  			UPDATE replacementOrders
  1261  			SET orderID = ?, orderExpires = ?
  1262  			WHERE id = ?`,
  1263  			orderID, orderExpires,
  1264  			existingID[0],
  1265  		)
  1266  		if err != nil {
  1267  			return fmt.Errorf("updating replacement order: %w", err)
  1268  		}
  1269  	} else {
  1270  		// Insert new replacementOrder row.
  1271  		_, err = db.ExecContext(ctx, `
  1272  			INSERT INTO replacementOrders (serial, orderID, orderExpires)
  1273  			VALUES (?, ?, ?)`,
  1274  			serial, orderID, orderExpires,
  1275  		)
  1276  		if err != nil {
  1277  			return fmt.Errorf("creating replacement order: %w", err)
  1278  		}
  1279  	}
  1280  	return nil
  1281  }
  1282  
  1283  // setReplacementOrderFinalized sets the replaced flag for the replacementOrder
  1284  // row matching the provided orderID to true. This function accepts a
  1285  // transaction so that the update can take place within the finalization
  1286  // transaction.
  1287  func setReplacementOrderFinalized(ctx context.Context, db db.Execer, orderID int64) error {
  1288  	_, err := db.ExecContext(ctx, `
  1289  		UPDATE replacementOrders
  1290  		SET replaced = true
  1291  		WHERE orderID = ?
  1292  		LIMIT 1`,
  1293  		orderID,
  1294  	)
  1295  	if err != nil {
  1296  		return err
  1297  	}
  1298  	return nil
  1299  }
  1300  
  1301  type identifierModel struct {
  1302  	Type  uint8  `db:"identifierType"`
  1303  	Value string `db:"identifierValue"`
  1304  }
  1305  
  1306  func newIdentifierModelFromPB(pb *corepb.Identifier) (identifierModel, error) {
  1307  	idType, ok := identifierTypeToUint[pb.Type]
  1308  	if !ok {
  1309  		return identifierModel{}, fmt.Errorf("unsupported identifier type %q", pb.Type)
  1310  	}
  1311  
  1312  	return identifierModel{
  1313  		Type:  idType,
  1314  		Value: pb.Value,
  1315  	}, nil
  1316  }
  1317  
  1318  func newPBFromIdentifierModel(id identifierModel) (*corepb.Identifier, error) {
  1319  	idType, ok := uintToIdentifierType[id.Type]
  1320  	if !ok {
  1321  		return nil, fmt.Errorf("unsupported identifier type %d", id.Type)
  1322  	}
  1323  
  1324  	return &corepb.Identifier{
  1325  		Type:  string(idType),
  1326  		Value: id.Value,
  1327  	}, nil
  1328  }
  1329  
  1330  func newIdentifierModelsFromPB(pbs []*corepb.Identifier) ([]identifierModel, error) {
  1331  	ids := make([]identifierModel, 0, len(pbs))
  1332  	for _, pb := range pbs {
  1333  		id, err := newIdentifierModelFromPB(pb)
  1334  		if err != nil {
  1335  			return nil, err
  1336  		}
  1337  		ids = append(ids, id)
  1338  	}
  1339  	return ids, nil
  1340  }
  1341  
  1342  func newPBFromIdentifierModels(ids []identifierModel) (*sapb.Identifiers, error) {
  1343  	pbs := make([]*corepb.Identifier, 0, len(ids))
  1344  	for _, id := range ids {
  1345  		pb, err := newPBFromIdentifierModel(id)
  1346  		if err != nil {
  1347  			return nil, err
  1348  		}
  1349  		pbs = append(pbs, pb)
  1350  	}
  1351  	return &sapb.Identifiers{Identifiers: pbs}, nil
  1352  }
  1353  
  1354  // buildIdentifierQueryConditions takes a slice of identifiers and returns a
  1355  // string (conditions to use within the prepared statement) and a slice of anys
  1356  // (arguments for the prepared statement), both to use within a WHERE clause for
  1357  // queries against the authz2 table.
  1358  //
  1359  // Although this function takes user-controlled input, it does not include any
  1360  // of that input directly in the returned SQL string. The resulting string
  1361  // contains only column names, boolean operators, and questionmark placeholders.
  1362  func buildIdentifierQueryConditions(idents identifier.ACMEIdentifiers) (string, []any) {
  1363  	if len(idents) == 0 {
  1364  		// No identifier values to check.
  1365  		return "FALSE", []any{}
  1366  	}
  1367  
  1368  	identsByType := map[identifier.IdentifierType][]string{}
  1369  	for _, id := range idents {
  1370  		identsByType[id.Type] = append(identsByType[id.Type], id.Value)
  1371  	}
  1372  
  1373  	var conditions []string
  1374  	var args []any
  1375  	for idType, idValues := range identsByType {
  1376  		conditions = append(conditions,
  1377  			fmt.Sprintf("identifierType = ? AND identifierValue IN (%s)",
  1378  				db.QuestionMarks(len(idValues)),
  1379  			),
  1380  		)
  1381  		args = append(args, identifierTypeToUint[string(idType)])
  1382  		for _, idValue := range idValues {
  1383  			args = append(args, idValue)
  1384  		}
  1385  	}
  1386  
  1387  	return strings.Join(conditions, " OR "), args
  1388  }
  1389  
  1390  // pausedModel represents a row in the paused table. It contains the
  1391  // registrationID of the paused account, the time the (account, identifier) pair
  1392  // was paused, and the time the pair was unpaused. The UnpausedAt field is
  1393  // nullable because the pair may not have been unpaused yet. A pair is
  1394  // considered paused if there is a matching row in the paused table with a NULL
  1395  // UnpausedAt time.
  1396  type pausedModel struct {
  1397  	identifierModel
  1398  	RegistrationID int64      `db:"registrationID"`
  1399  	PausedAt       time.Time  `db:"pausedAt"`
  1400  	UnpausedAt     *time.Time `db:"unpausedAt"`
  1401  }
  1402  
  1403  type overrideModel struct {
  1404  	LimitEnum int64     `db:"limitEnum"`
  1405  	BucketKey string    `db:"bucketKey"`
  1406  	Comment   string    `db:"comment"`
  1407  	PeriodNS  int64     `db:"periodNS"`
  1408  	Count     int64     `db:"count"`
  1409  	Burst     int64     `db:"burst"`
  1410  	UpdatedAt time.Time `db:"updatedAt"`
  1411  	Enabled   bool      `db:"enabled"`
  1412  }
  1413  
  1414  func overrideModelForPB(pb *sapb.RateLimitOverride, updatedAt time.Time, enabled bool) overrideModel {
  1415  	return overrideModel{
  1416  		LimitEnum: pb.LimitEnum,
  1417  		BucketKey: pb.BucketKey,
  1418  		Comment:   pb.Comment,
  1419  		PeriodNS:  pb.Period.AsDuration().Nanoseconds(),
  1420  		Count:     pb.Count,
  1421  		Burst:     pb.Burst,
  1422  		UpdatedAt: updatedAt,
  1423  		Enabled:   enabled,
  1424  	}
  1425  }
  1426  
  1427  func newPBFromOverrideModel(m *overrideModel) *sapb.RateLimitOverride {
  1428  	return &sapb.RateLimitOverride{
  1429  		LimitEnum: m.LimitEnum,
  1430  		BucketKey: m.BucketKey,
  1431  		Comment:   m.Comment,
  1432  		Period:    durationpb.New(time.Duration(m.PeriodNS)),
  1433  		Count:     m.Count,
  1434  		Burst:     m.Burst,
  1435  	}
  1436  }