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

     1  package sa
     2  
     3  import (
     4  	"context"
     5  	"crypto/x509"
     6  	"database/sql"
     7  	"encoding/json"
     8  	"errors"
     9  	"fmt"
    10  	"google.golang.org/protobuf/proto"
    11  	"strings"
    12  	"time"
    13  
    14  	"github.com/go-jose/go-jose/v4"
    15  	"github.com/jmhodges/clock"
    16  	"github.com/prometheus/client_golang/prometheus"
    17  	"github.com/prometheus/client_golang/prometheus/promauto"
    18  	"google.golang.org/protobuf/types/known/emptypb"
    19  	"google.golang.org/protobuf/types/known/timestamppb"
    20  
    21  	"github.com/letsencrypt/boulder/core"
    22  	corepb "github.com/letsencrypt/boulder/core/proto"
    23  	"github.com/letsencrypt/boulder/db"
    24  	berrors "github.com/letsencrypt/boulder/errors"
    25  	"github.com/letsencrypt/boulder/features"
    26  	bgrpc "github.com/letsencrypt/boulder/grpc"
    27  	"github.com/letsencrypt/boulder/identifier"
    28  	blog "github.com/letsencrypt/boulder/log"
    29  	"github.com/letsencrypt/boulder/revocation"
    30  	sapb "github.com/letsencrypt/boulder/sa/proto"
    31  	"github.com/letsencrypt/boulder/unpause"
    32  )
    33  
    34  var (
    35  	errIncompleteRequest = errors.New("incomplete gRPC request message")
    36  )
    37  
    38  // SQLStorageAuthority defines a Storage Authority.
    39  //
    40  // Note that although SQLStorageAuthority does have methods wrapping all of the
    41  // read-only methods provided by the SQLStorageAuthorityRO, those wrapper
    42  // implementations are in saro.go, next to the real implementations.
    43  type SQLStorageAuthority struct {
    44  	sapb.UnsafeStorageAuthorityServer
    45  
    46  	*SQLStorageAuthorityRO
    47  
    48  	dbMap *db.WrappedMap
    49  
    50  	// rateLimitWriteErrors is a Counter for the number of times
    51  	// a ratelimit update transaction failed during AddCertificate request
    52  	// processing. We do not fail the overall AddCertificate call when ratelimit
    53  	// transactions fail and so use this stat to maintain visibility into the rate
    54  	// this occurs.
    55  	rateLimitWriteErrors prometheus.Counter
    56  }
    57  
    58  var _ sapb.StorageAuthorityServer = (*SQLStorageAuthority)(nil)
    59  
    60  // NewSQLStorageAuthorityWrapping provides persistence using a SQL backend for
    61  // Boulder. It takes a read-only storage authority to wrap, which is useful if
    62  // you are constructing both types of implementations and want to share
    63  // read-only database connections between them.
    64  func NewSQLStorageAuthorityWrapping(
    65  	ssaro *SQLStorageAuthorityRO,
    66  	dbMap *db.WrappedMap,
    67  	stats prometheus.Registerer,
    68  ) (*SQLStorageAuthority, error) {
    69  	rateLimitWriteErrors := promauto.With(stats).NewCounter(prometheus.CounterOpts{
    70  		Name: "rate_limit_write_errors",
    71  		Help: "number of failed ratelimit update transactions during AddCertificate",
    72  	})
    73  
    74  	ssa := &SQLStorageAuthority{
    75  		SQLStorageAuthorityRO: ssaro,
    76  		dbMap:                 dbMap,
    77  		rateLimitWriteErrors:  rateLimitWriteErrors,
    78  	}
    79  
    80  	return ssa, nil
    81  }
    82  
    83  // NewSQLStorageAuthority provides persistence using a SQL backend for
    84  // Boulder. It constructs its own read-only storage authority to wrap.
    85  func NewSQLStorageAuthority(
    86  	dbMap *db.WrappedMap,
    87  	dbReadOnlyMap *db.WrappedMap,
    88  	dbIncidentsMap *db.WrappedMap,
    89  	parallelismPerRPC int,
    90  	lagFactor time.Duration,
    91  	clk clock.Clock,
    92  	logger blog.Logger,
    93  	stats prometheus.Registerer,
    94  ) (*SQLStorageAuthority, error) {
    95  	ssaro, err := NewSQLStorageAuthorityRO(
    96  		dbReadOnlyMap, dbIncidentsMap, stats, parallelismPerRPC, lagFactor, clk, logger)
    97  	if err != nil {
    98  		return nil, err
    99  	}
   100  
   101  	return NewSQLStorageAuthorityWrapping(ssaro, dbMap, stats)
   102  }
   103  
   104  // NewRegistration stores a new Registration
   105  func (ssa *SQLStorageAuthority) NewRegistration(ctx context.Context, req *corepb.Registration) (*corepb.Registration, error) {
   106  	if len(req.Key) == 0 {
   107  		return nil, errIncompleteRequest
   108  	}
   109  
   110  	reg, err := registrationPbToModel(req)
   111  	if err != nil {
   112  		return nil, err
   113  	}
   114  
   115  	reg.CreatedAt = ssa.clk.Now()
   116  
   117  	err = ssa.dbMap.Insert(ctx, reg)
   118  	if err != nil {
   119  		if db.IsDuplicate(err) {
   120  			// duplicate entry error can only happen when jwk_sha256 collides, indicate
   121  			// to caller that the provided key is already in use
   122  			return nil, berrors.DuplicateError("key is already in use for a different account")
   123  		}
   124  		return nil, err
   125  	}
   126  	return registrationModelToPb(reg)
   127  }
   128  
   129  // UpdateRegistrationKey stores an updated key in a Registration.
   130  func (ssa *SQLStorageAuthority) UpdateRegistrationKey(ctx context.Context, req *sapb.UpdateRegistrationKeyRequest) (*corepb.Registration, error) {
   131  	if core.IsAnyNilOrZero(req.RegistrationID, req.Jwk) {
   132  		return nil, errIncompleteRequest
   133  	}
   134  
   135  	// Even though we don't need to convert from JSON to an in-memory JSONWebKey
   136  	// for the sake of the `Key` field, we do need to do the conversion in order
   137  	// to compute the SHA256 key digest.
   138  	var jwk jose.JSONWebKey
   139  	err := jwk.UnmarshalJSON(req.Jwk)
   140  	if err != nil {
   141  		return nil, fmt.Errorf("parsing JWK: %w", err)
   142  	}
   143  	sha, err := core.KeyDigestB64(jwk.Key)
   144  	if err != nil {
   145  		return nil, fmt.Errorf("computing key digest: %w", err)
   146  	}
   147  
   148  	result, overallError := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (any, error) {
   149  		result, err := tx.ExecContext(ctx,
   150  			"UPDATE registrations SET jwk = ?, jwk_sha256 = ? WHERE id = ? LIMIT 1",
   151  			req.Jwk,
   152  			sha,
   153  			req.RegistrationID,
   154  		)
   155  		if err != nil {
   156  			if db.IsDuplicate(err) {
   157  				// duplicate entry error can only happen when jwk_sha256 collides, indicate
   158  				// to caller that the provided key is already in use
   159  				return nil, berrors.DuplicateError("key is already in use for a different account")
   160  			}
   161  			return nil, err
   162  		}
   163  		rowsAffected, err := result.RowsAffected()
   164  		if err != nil || rowsAffected != 1 {
   165  			return nil, berrors.InternalServerError("no registration ID '%d' updated with new jwk", req.RegistrationID)
   166  		}
   167  
   168  		updatedRegistrationModel, err := selectRegistration(ctx, tx, "id", req.RegistrationID)
   169  		if err != nil {
   170  			if db.IsNoRows(err) {
   171  				return nil, berrors.NotFoundError("registration with ID '%d' not found", req.RegistrationID)
   172  			}
   173  			return nil, err
   174  		}
   175  		updatedRegistration, err := registrationModelToPb(updatedRegistrationModel)
   176  		if err != nil {
   177  			return nil, err
   178  		}
   179  
   180  		return updatedRegistration, nil
   181  	})
   182  	if overallError != nil {
   183  		return nil, overallError
   184  	}
   185  
   186  	return result.(*corepb.Registration), nil
   187  }
   188  
   189  // AddSerial writes a record of a serial number generation to the DB.
   190  func (ssa *SQLStorageAuthority) AddSerial(ctx context.Context, req *sapb.AddSerialRequest) (*emptypb.Empty, error) {
   191  	if core.IsAnyNilOrZero(req.Serial, req.RegID, req.Created, req.Expires) {
   192  		return nil, errIncompleteRequest
   193  	}
   194  	err := ssa.dbMap.Insert(ctx, &recordedSerialModel{
   195  		Serial:         req.Serial,
   196  		RegistrationID: req.RegID,
   197  		Created:        req.Created.AsTime(),
   198  		Expires:        req.Expires.AsTime(),
   199  	})
   200  	if err != nil {
   201  		return nil, err
   202  	}
   203  	return &emptypb.Empty{}, nil
   204  }
   205  
   206  // AddPrecertificate writes a record of a linting certificate to the database.
   207  //
   208  // Note: The name "AddPrecertificate" is a historical artifact, and this is now
   209  // always called with a linting certificate. See #6807.
   210  //
   211  // Note: this is not idempotent: it does not protect against inserting the same
   212  // certificate multiple times. Calling code needs to first insert the cert's
   213  // serial into the Serials table to ensure uniqueness.
   214  func (ssa *SQLStorageAuthority) AddPrecertificate(ctx context.Context, req *sapb.AddCertificateRequest) (*emptypb.Empty, error) {
   215  	if core.IsAnyNilOrZero(req.Der, req.RegID, req.IssuerNameID, req.Issued) {
   216  		return nil, errIncompleteRequest
   217  	}
   218  	parsed, err := x509.ParseCertificate(req.Der)
   219  	if err != nil {
   220  		return nil, err
   221  	}
   222  	serialHex := core.SerialToString(parsed.SerialNumber)
   223  
   224  	preCertModel := &lintingCertModel{
   225  		Serial:         serialHex,
   226  		RegistrationID: req.RegID,
   227  		DER:            req.Der,
   228  		Issued:         req.Issued.AsTime(),
   229  		Expires:        parsed.NotAfter,
   230  	}
   231  
   232  	_, overallError := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (any, error) {
   233  		// Select to see if precert exists
   234  		var row struct {
   235  			Count int64
   236  		}
   237  		err := tx.SelectOne(ctx, &row, "SELECT COUNT(*) as count FROM precertificates WHERE serial=?", serialHex)
   238  		if err != nil {
   239  			return nil, err
   240  		}
   241  		if row.Count > 0 {
   242  			return nil, berrors.DuplicateError("cannot add a duplicate cert")
   243  		}
   244  
   245  		err = tx.Insert(ctx, preCertModel)
   246  		if err != nil {
   247  			return nil, err
   248  		}
   249  
   250  		// An arbitrary, but valid date for fields revokedDate and lastExpirationNagSent.
   251  		// These fields in the database are NOT NULL so we can't omit them; and we don't
   252  		// want to pass `time.Time{}` because that results in inserts of `0000-00-00`, which
   253  		// is forbidden in strict mode (when NO_ZERO_DATE is on).
   254  		dummyDate := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)
   255  
   256  		status := core.OCSPStatusGood
   257  		cs := &certificateStatusModel{
   258  			Serial:                serialHex,
   259  			Status:                status,
   260  			OCSPLastUpdated:       ssa.clk.Now(),
   261  			RevokedDate:           dummyDate,
   262  			RevokedReason:         0,
   263  			LastExpirationNagSent: dummyDate,
   264  			NotAfter:              parsed.NotAfter,
   265  			IsExpired:             false,
   266  			IssuerID:              req.IssuerNameID,
   267  		}
   268  		err = ssa.dbMap.Insert(ctx, cs)
   269  		if err != nil {
   270  			return nil, err
   271  		}
   272  
   273  		idents := identifier.FromCert(parsed)
   274  
   275  		isRenewal, err := ssa.checkFQDNSetExists(
   276  			ctx,
   277  			tx.SelectOne,
   278  			idents)
   279  		if err != nil {
   280  			return nil, err
   281  		}
   282  
   283  		err = addIssuedNames(ctx, tx, parsed, isRenewal)
   284  		if err != nil {
   285  			return nil, err
   286  		}
   287  
   288  		err = addKeyHash(ctx, tx, parsed)
   289  		if err != nil {
   290  			return nil, err
   291  		}
   292  
   293  		return nil, nil
   294  	})
   295  	if overallError != nil {
   296  		return nil, overallError
   297  	}
   298  
   299  	return &emptypb.Empty{}, nil
   300  }
   301  
   302  // AddCertificate stores an issued certificate, returning an error if it is a
   303  // duplicate or if any other failure occurs.
   304  func (ssa *SQLStorageAuthority) AddCertificate(ctx context.Context, req *sapb.AddCertificateRequest) (*emptypb.Empty, error) {
   305  	if core.IsAnyNilOrZero(req.Der, req.RegID, req.Issued) {
   306  		return nil, errIncompleteRequest
   307  	}
   308  	parsedCertificate, err := x509.ParseCertificate(req.Der)
   309  	if err != nil {
   310  		return nil, err
   311  	}
   312  	digest := core.Fingerprint256(req.Der)
   313  	serial := core.SerialToString(parsedCertificate.SerialNumber)
   314  
   315  	cert := &core.Certificate{
   316  		RegistrationID: req.RegID,
   317  		Serial:         serial,
   318  		Digest:         digest,
   319  		DER:            req.Der,
   320  		Issued:         req.Issued.AsTime(),
   321  		Expires:        parsedCertificate.NotAfter,
   322  	}
   323  
   324  	_, overallError := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (any, error) {
   325  		// Select to see if cert exists
   326  		var row struct {
   327  			Count int64
   328  		}
   329  		err := tx.SelectOne(ctx, &row, "SELECT COUNT(*) as count FROM certificates WHERE serial=?", serial)
   330  		if err != nil {
   331  			return nil, err
   332  		}
   333  		if row.Count > 0 {
   334  			return nil, berrors.DuplicateError("cannot add a duplicate cert")
   335  		}
   336  
   337  		// Save the final certificate
   338  		err = tx.Insert(ctx, cert)
   339  		if err != nil {
   340  			return nil, err
   341  		}
   342  
   343  		return nil, err
   344  	})
   345  	if overallError != nil {
   346  		return nil, overallError
   347  	}
   348  
   349  	// In a separate transaction, perform the work required to update the table
   350  	// used for order reuse. Since the effect of failing the write is just a
   351  	// missed opportunity to reuse an order, we choose to not fail the
   352  	// AddCertificate operation if this update transaction fails.
   353  	_, fqdnTransactionErr := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (any, error) {
   354  		// Update the FQDN sets now that there is a final certificate to ensure
   355  		// reuse is determined correctly.
   356  		err = addFQDNSet(
   357  			ctx,
   358  			tx,
   359  			identifier.FromCert(parsedCertificate),
   360  			core.SerialToString(parsedCertificate.SerialNumber),
   361  			parsedCertificate.NotBefore,
   362  			parsedCertificate.NotAfter,
   363  		)
   364  		if err != nil {
   365  			return nil, err
   366  		}
   367  
   368  		return nil, nil
   369  	})
   370  	// If the FQDN sets transaction failed, increment a stat and log a warning
   371  	// but don't return an error from AddCertificate.
   372  	if fqdnTransactionErr != nil {
   373  		ssa.rateLimitWriteErrors.Inc()
   374  		ssa.log.AuditErrf("failed AddCertificate FQDN sets insert transaction: %v", fqdnTransactionErr)
   375  	}
   376  
   377  	return &emptypb.Empty{}, nil
   378  }
   379  
   380  // DeactivateRegistration deactivates a currently valid registration
   381  func (ssa *SQLStorageAuthority) DeactivateRegistration(ctx context.Context, req *sapb.RegistrationID) (*corepb.Registration, error) {
   382  	if req == nil || req.Id == 0 {
   383  		return nil, errIncompleteRequest
   384  	}
   385  
   386  	result, overallError := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (any, error) {
   387  		result, err := tx.ExecContext(ctx,
   388  			"UPDATE registrations SET status = ? WHERE status = ? AND id = ? LIMIT 1",
   389  			string(core.StatusDeactivated),
   390  			string(core.StatusValid),
   391  			req.Id,
   392  		)
   393  		if err != nil {
   394  			return nil, fmt.Errorf("deactivating account %d: %w", req.Id, err)
   395  		}
   396  		rowsAffected, err := result.RowsAffected()
   397  		if err != nil {
   398  			return nil, fmt.Errorf("deactivating account %d: %w", req.Id, err)
   399  		}
   400  		if rowsAffected == 0 {
   401  			return nil, berrors.NotFoundError("no active account with id %d", req.Id)
   402  		} else if rowsAffected > 1 {
   403  			return nil, berrors.InternalServerError("unexpectedly deactivated multiple accounts with id %d", req.Id)
   404  		}
   405  
   406  		updatedRegistrationModel, err := selectRegistration(ctx, tx, "id", req.Id)
   407  		if err != nil {
   408  			if db.IsNoRows(err) {
   409  				return nil, berrors.NotFoundError("fetching account %d: no rows found", req.Id)
   410  			}
   411  			return nil, fmt.Errorf("fetching account %d: %w", req.Id, err)
   412  		}
   413  
   414  		updatedRegistration, err := registrationModelToPb(updatedRegistrationModel)
   415  		if err != nil {
   416  			return nil, err
   417  		}
   418  
   419  		return updatedRegistration, nil
   420  	})
   421  	if overallError != nil {
   422  		return nil, overallError
   423  	}
   424  
   425  	res, ok := result.(*corepb.Registration)
   426  	if !ok {
   427  		return nil, fmt.Errorf("unexpected casting failure in DeactivateRegistration")
   428  	}
   429  
   430  	return res, nil
   431  }
   432  
   433  // DeactivateAuthorization2 deactivates a currently valid or pending authorization.
   434  func (ssa *SQLStorageAuthority) DeactivateAuthorization2(ctx context.Context, req *sapb.AuthorizationID2) (*emptypb.Empty, error) {
   435  	if req.Id == 0 {
   436  		return nil, errIncompleteRequest
   437  	}
   438  
   439  	_, err := ssa.dbMap.ExecContext(ctx,
   440  		`UPDATE authz2 SET status = :deactivated WHERE id = :id and status IN (:valid,:pending)`,
   441  		map[string]any{
   442  			"deactivated": statusUint(core.StatusDeactivated),
   443  			"id":          req.Id,
   444  			"valid":       statusUint(core.StatusValid),
   445  			"pending":     statusUint(core.StatusPending),
   446  		},
   447  	)
   448  	if err != nil {
   449  		return nil, err
   450  	}
   451  	return &emptypb.Empty{}, nil
   452  }
   453  
   454  // NewOrderAndAuthzs adds the given authorizations to the database, adds their
   455  // autogenerated IDs to the given order, and then adds the order to the db.
   456  // This is done inside a single transaction to prevent situations where new
   457  // authorizations are created, but then their corresponding order is never
   458  // created, leading to "invisible" pending authorizations.
   459  func (ssa *SQLStorageAuthority) NewOrderAndAuthzs(ctx context.Context, req *sapb.NewOrderAndAuthzsRequest) (*corepb.Order, error) {
   460  	if req.NewOrder == nil {
   461  		return nil, errIncompleteRequest
   462  	}
   463  	if len(req.NewAuthzs) == 0 && len(req.NewOrder.V2Authorizations) == 0 {
   464  		return nil, errIncompleteRequest
   465  	}
   466  
   467  	for _, authz := range req.NewAuthzs {
   468  		if authz.RegistrationID != req.NewOrder.RegistrationID {
   469  			// This is a belt-and-suspenders check. These were just created by the RA,
   470  			// so their RegIDs should match. But if they don't, the consequences would
   471  			// be very bad, so we do an extra check here.
   472  			return nil, errors.New("new order and authzs must all be associated with same account")
   473  		}
   474  	}
   475  
   476  	output, err := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (any, error) {
   477  		// First, insert all of the new authorizations and record their IDs.
   478  		newAuthzIDs := make([]int64, 0, len(req.NewAuthzs))
   479  		for _, authz := range req.NewAuthzs {
   480  			am, err := newAuthzReqToModel(authz, req.NewOrder.CertificateProfileName)
   481  			if err != nil {
   482  				return nil, err
   483  			}
   484  			err = tx.Insert(ctx, am)
   485  			if err != nil {
   486  				return nil, err
   487  			}
   488  			newAuthzIDs = append(newAuthzIDs, am.ID)
   489  		}
   490  
   491  		// Combine the already-existing and newly-created authzs.
   492  		allAuthzIds := append(req.NewOrder.V2Authorizations, newAuthzIDs...)
   493  
   494  		// Second, insert the new order.
   495  		created := ssa.clk.Now()
   496  		var encodedAuthzs []byte
   497  		var err error
   498  		if features.Get().StoreAuthzsInOrders {
   499  			encodedAuthzs, err = proto.Marshal(&sapb.Authzs{
   500  				AuthzIDs: allAuthzIds,
   501  			})
   502  			if err != nil {
   503  				return nil, err
   504  			}
   505  		}
   506  
   507  		om := orderModel{
   508  			RegistrationID:         req.NewOrder.RegistrationID,
   509  			Expires:                req.NewOrder.Expires.AsTime(),
   510  			Created:                created,
   511  			CertificateProfileName: &req.NewOrder.CertificateProfileName,
   512  			Replaces:               &req.NewOrder.Replaces,
   513  			Authzs:                 encodedAuthzs,
   514  		}
   515  		err = tx.Insert(ctx, &om)
   516  		if err != nil {
   517  			return nil, err
   518  		}
   519  		orderID := om.ID
   520  
   521  		// Third, insert all of the orderToAuthz relations.
   522  		inserter, err := db.NewMultiInserter("orderToAuthz2", []string{"orderID", "authzID"})
   523  		if err != nil {
   524  			return nil, err
   525  		}
   526  		for _, id := range allAuthzIds {
   527  			err := inserter.Add([]any{orderID, id})
   528  			if err != nil {
   529  				return nil, err
   530  			}
   531  		}
   532  		err = inserter.Insert(ctx, tx)
   533  		if err != nil {
   534  			return nil, err
   535  		}
   536  
   537  		// Fourth, insert the FQDNSet entry for the order.
   538  		err = addOrderFQDNSet(ctx, tx, identifier.FromProtoSlice(req.NewOrder.Identifiers), orderID, req.NewOrder.RegistrationID, req.NewOrder.Expires.AsTime())
   539  		if err != nil {
   540  			return nil, err
   541  		}
   542  
   543  		if req.NewOrder.ReplacesSerial != "" {
   544  			// Update the replacementOrders table to indicate that this order
   545  			// replaces the provided certificate serial.
   546  			err := addReplacementOrder(ctx, tx, req.NewOrder.ReplacesSerial, orderID, req.NewOrder.Expires.AsTime())
   547  			if err != nil {
   548  				return nil, err
   549  			}
   550  		}
   551  
   552  		// Get the partial Authorization objects for the order
   553  		authzValidityInfo, err := getAuthorizationStatuses(ctx, tx, allAuthzIds)
   554  		// If there was an error getting the authorizations, return it immediately
   555  		if err != nil {
   556  			return nil, err
   557  		}
   558  
   559  		// Finally, build the overall Order PB.
   560  		res := &corepb.Order{
   561  			// ID and Created were auto-populated on the order model when it was inserted.
   562  			Id:      orderID,
   563  			Created: timestamppb.New(created),
   564  			// These are carried over from the original request unchanged.
   565  			RegistrationID: req.NewOrder.RegistrationID,
   566  			Expires:        req.NewOrder.Expires,
   567  			Identifiers:    req.NewOrder.Identifiers,
   568  			// This includes both reused and newly created authz IDs.
   569  			V2Authorizations: allAuthzIds,
   570  			// A new order is never processing because it can't be finalized yet.
   571  			BeganProcessing: false,
   572  			// An empty string is allowed. When the RA retrieves the order and
   573  			// transmits it to the CA, the empty string will take the value of
   574  			// DefaultCertProfileName from the //issuance package.
   575  			CertificateProfileName: req.NewOrder.CertificateProfileName,
   576  			Replaces:               req.NewOrder.Replaces,
   577  		}
   578  
   579  		// Calculate the order status before returning it. Since it may have reused
   580  		// all valid authorizations the order may be "born" in a ready status.
   581  		status, err := statusForOrder(res, authzValidityInfo, ssa.clk.Now())
   582  		if err != nil {
   583  			return nil, err
   584  		}
   585  		res.Status = status
   586  
   587  		return res, nil
   588  	})
   589  	if err != nil {
   590  		return nil, err
   591  	}
   592  
   593  	order, ok := output.(*corepb.Order)
   594  	if !ok {
   595  		return nil, fmt.Errorf("casting error in NewOrderAndAuthzs")
   596  	}
   597  
   598  	return order, nil
   599  }
   600  
   601  // SetOrderProcessing updates an order from pending status to processing
   602  // status by updating the `beganProcessing` field of the corresponding
   603  // Order table row in the DB.
   604  func (ssa *SQLStorageAuthority) SetOrderProcessing(ctx context.Context, req *sapb.OrderRequest) (*emptypb.Empty, error) {
   605  	if req.Id == 0 {
   606  		return nil, errIncompleteRequest
   607  	}
   608  	_, overallError := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (any, error) {
   609  		result, err := tx.ExecContext(ctx, `
   610  		UPDATE orders
   611  		SET beganProcessing = ?
   612  		WHERE id = ?
   613  		AND beganProcessing = ?`,
   614  			true,
   615  			req.Id,
   616  			false)
   617  		if err != nil {
   618  			return nil, berrors.InternalServerError("error updating order to beganProcessing status")
   619  		}
   620  
   621  		n, err := result.RowsAffected()
   622  		if err != nil || n == 0 {
   623  			return nil, berrors.OrderNotReadyError("Order was already processing. This may indicate your client finalized the same order multiple times, possibly due to a client bug.")
   624  		}
   625  
   626  		return nil, nil
   627  	})
   628  	if overallError != nil {
   629  		return nil, overallError
   630  	}
   631  	return &emptypb.Empty{}, nil
   632  }
   633  
   634  // SetOrderError updates a provided Order's error field.
   635  func (ssa *SQLStorageAuthority) SetOrderError(ctx context.Context, req *sapb.SetOrderErrorRequest) (*emptypb.Empty, error) {
   636  	if req.Id == 0 || req.Error == nil {
   637  		return nil, errIncompleteRequest
   638  	}
   639  
   640  	errJSON, err := json.Marshal(req.Error)
   641  	if err != nil {
   642  		return nil, err
   643  	}
   644  	if len(errJSON) > mediumBlobSize {
   645  		return nil, fmt.Errorf("error object is too large to store in the database")
   646  	}
   647  
   648  	_, overallError := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (any, error) {
   649  		result, err := tx.ExecContext(ctx, `
   650  		UPDATE orders
   651  		SET error = ?
   652  		WHERE id = ?`,
   653  			errJSON,
   654  			req.Id)
   655  		if err != nil {
   656  			return nil, berrors.InternalServerError("error updating order error field")
   657  		}
   658  
   659  		n, err := result.RowsAffected()
   660  		if err != nil || n == 0 {
   661  			return nil, berrors.InternalServerError("no order updated with new error field")
   662  		}
   663  
   664  		return nil, nil
   665  	})
   666  	if overallError != nil {
   667  		return nil, overallError
   668  	}
   669  	return &emptypb.Empty{}, nil
   670  }
   671  
   672  // FinalizeOrder finalizes a provided *corepb.Order by persisting the
   673  // CertificateSerial and a valid status to the database. No fields other than
   674  // CertificateSerial and the order ID on the provided order are processed (e.g.
   675  // this is not a generic update RPC).
   676  func (ssa *SQLStorageAuthority) FinalizeOrder(ctx context.Context, req *sapb.FinalizeOrderRequest) (*emptypb.Empty, error) {
   677  	if req.Id == 0 || req.CertificateSerial == "" {
   678  		return nil, errIncompleteRequest
   679  	}
   680  	_, overallError := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (any, error) {
   681  		result, err := tx.ExecContext(ctx, `
   682  		UPDATE orders
   683  		SET certificateSerial = ?
   684  		WHERE id = ? AND
   685  		beganProcessing = true`,
   686  			req.CertificateSerial,
   687  			req.Id)
   688  		if err != nil {
   689  			return nil, berrors.InternalServerError("error updating order for finalization")
   690  		}
   691  
   692  		n, err := result.RowsAffected()
   693  		if err != nil || n == 0 {
   694  			return nil, berrors.InternalServerError("no order updated for finalization")
   695  		}
   696  
   697  		// Delete the orderFQDNSet row for the order now that it has been finalized.
   698  		// We use this table for order reuse and should not reuse a finalized order.
   699  		err = deleteOrderFQDNSet(ctx, tx, req.Id)
   700  		if err != nil {
   701  			return nil, err
   702  		}
   703  
   704  		err = setReplacementOrderFinalized(ctx, tx, req.Id)
   705  		if err != nil {
   706  			return nil, err
   707  		}
   708  
   709  		return nil, nil
   710  	})
   711  	if overallError != nil {
   712  		return nil, overallError
   713  	}
   714  	return &emptypb.Empty{}, nil
   715  }
   716  
   717  // FinalizeAuthorization2 moves a pending authorization to either the valid or invalid status. If
   718  // the authorization is being moved to invalid the validationError field must be set. If the
   719  // authorization is being moved to valid the validationRecord and expires fields must be set.
   720  func (ssa *SQLStorageAuthority) FinalizeAuthorization2(ctx context.Context, req *sapb.FinalizeAuthorizationRequest) (*emptypb.Empty, error) {
   721  	if core.IsAnyNilOrZero(req.Status, req.Attempted, req.Id, req.Expires) {
   722  		return nil, errIncompleteRequest
   723  	}
   724  
   725  	if req.Status != string(core.StatusValid) && req.Status != string(core.StatusInvalid) {
   726  		return nil, berrors.InternalServerError("authorization must have status valid or invalid")
   727  	}
   728  	query := `UPDATE authz2 SET
   729  		status = :status,
   730  		attempted = :attempted,
   731  		attemptedAt = :attemptedAt,
   732  		validationRecord = :validationRecord,
   733  		validationError = :validationError,
   734  		expires = :expires
   735  		WHERE id = :id AND status = :pending`
   736  	var validationRecords []core.ValidationRecord
   737  	for _, recordPB := range req.ValidationRecords {
   738  		record, err := bgrpc.PBToValidationRecord(recordPB)
   739  		if err != nil {
   740  			return nil, err
   741  		}
   742  		if req.Attempted == string(core.ChallengeTypeHTTP01) {
   743  			// Remove these fields because they can be rehydrated later
   744  			// on from the URL field.
   745  			record.Hostname = ""
   746  			record.Port = ""
   747  		}
   748  		validationRecords = append(validationRecords, record)
   749  	}
   750  	vrJSON, err := json.Marshal(validationRecords)
   751  	if err != nil {
   752  		return nil, err
   753  	}
   754  	var veJSON []byte
   755  	if req.ValidationError != nil {
   756  		validationError, err := bgrpc.PBToProblemDetails(req.ValidationError)
   757  		if err != nil {
   758  			return nil, err
   759  		}
   760  		j, err := json.Marshal(validationError)
   761  		if err != nil {
   762  			return nil, err
   763  		}
   764  		veJSON = j
   765  	}
   766  	// Check to see if the AttemptedAt time is non zero and convert to
   767  	// *time.Time if so. If it is zero, leave nil and don't convert. Keep the
   768  	// database attemptedAt field Null instead of 1970-01-01 00:00:00.
   769  	var attemptedTime *time.Time
   770  	if !core.IsAnyNilOrZero(req.AttemptedAt) {
   771  		val := req.AttemptedAt.AsTime()
   772  		attemptedTime = &val
   773  	}
   774  	params := map[string]any{
   775  		"status":           statusToUint[core.AcmeStatus(req.Status)],
   776  		"attempted":        challTypeToUint[req.Attempted],
   777  		"attemptedAt":      attemptedTime,
   778  		"validationRecord": vrJSON,
   779  		"id":               req.Id,
   780  		"pending":          statusUint(core.StatusPending),
   781  		"expires":          req.Expires.AsTime(),
   782  		// if req.ValidationError is nil veJSON should also be nil
   783  		// which should result in a NULL field
   784  		"validationError": veJSON,
   785  	}
   786  
   787  	res, err := ssa.dbMap.ExecContext(ctx, query, params)
   788  	if err != nil {
   789  		return nil, err
   790  	}
   791  	rows, err := res.RowsAffected()
   792  	if err != nil {
   793  		return nil, err
   794  	}
   795  	if rows == 0 {
   796  		return nil, berrors.NotFoundError("no pending authorization with id %d", req.Id)
   797  	} else if rows > 1 {
   798  		return nil, berrors.InternalServerError("multiple rows updated for authorization id %d", req.Id)
   799  	}
   800  	return &emptypb.Empty{}, nil
   801  }
   802  
   803  // addRevokedCertificate is a helper used by both RevokeCertificate and
   804  // UpdateRevokedCertificate. It inserts a new row into the revokedCertificates
   805  // table based on the contents of the input request. The second argument must be
   806  // a transaction object so that it is safe to conduct multiple queries with a
   807  // consistent view of the database. It must only be called when the request
   808  // specifies a non-zero ShardIdx.
   809  func addRevokedCertificate(ctx context.Context, tx db.Executor, req *sapb.RevokeCertificateRequest, revokedDate time.Time) error {
   810  	if req.ShardIdx == 0 {
   811  		return errors.New("cannot add revoked certificate with shard index 0")
   812  	}
   813  
   814  	var serial struct {
   815  		Expires time.Time
   816  	}
   817  	err := tx.SelectOne(
   818  		ctx, &serial, `SELECT expires FROM serials WHERE serial = ?`, req.Serial)
   819  	if err != nil {
   820  		return fmt.Errorf("retrieving revoked certificate expiration: %w", err)
   821  	}
   822  
   823  	err = tx.Insert(ctx, &revokedCertModel{
   824  		IssuerID:      req.IssuerID,
   825  		Serial:        req.Serial,
   826  		ShardIdx:      req.ShardIdx,
   827  		RevokedDate:   revokedDate,
   828  		RevokedReason: revocation.Reason(req.Reason),
   829  		// Round the notAfter up to the next hour, to reduce index size while still
   830  		// ensuring we correctly serve revocation info past the actual expiration.
   831  		NotAfterHour: serial.Expires.Add(time.Hour).Truncate(time.Hour),
   832  	})
   833  	if err != nil {
   834  		return fmt.Errorf("inserting revoked certificate row: %w", err)
   835  	}
   836  
   837  	return nil
   838  }
   839  
   840  // RevokeCertificate stores revocation information about a certificate. It will only store this
   841  // information if the certificate is not already marked as revoked.
   842  //
   843  // If ShardIdx is non-zero, RevokeCertificate also writes an entry for this certificate to
   844  // the revokedCertificates table, with the provided shard number.
   845  func (ssa *SQLStorageAuthority) RevokeCertificate(ctx context.Context, req *sapb.RevokeCertificateRequest) (*emptypb.Empty, error) {
   846  	if core.IsAnyNilOrZero(req.Serial, req.IssuerID, req.Date, req.ShardIdx) {
   847  		return nil, errIncompleteRequest
   848  	}
   849  
   850  	_, overallError := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (any, error) {
   851  		revokedDate := req.Date.AsTime()
   852  
   853  		res, err := tx.ExecContext(ctx,
   854  			`UPDATE certificateStatus SET
   855  				status = ?,
   856  				revokedReason = ?,
   857  				revokedDate = ?,
   858  				ocspLastUpdated = ?
   859  			WHERE serial = ? AND status != ?`,
   860  			string(core.OCSPStatusRevoked),
   861  			revocation.Reason(req.Reason),
   862  			revokedDate,
   863  			revokedDate,
   864  			req.Serial,
   865  			string(core.OCSPStatusRevoked),
   866  		)
   867  		if err != nil {
   868  			return nil, err
   869  		}
   870  		rows, err := res.RowsAffected()
   871  		if err != nil {
   872  			return nil, err
   873  		}
   874  		if rows == 0 {
   875  			return nil, berrors.AlreadyRevokedError("no certificate with serial %s and status other than %s", req.Serial, string(core.OCSPStatusRevoked))
   876  		}
   877  
   878  		err = addRevokedCertificate(ctx, tx, req, revokedDate)
   879  		if err != nil {
   880  			return nil, err
   881  		}
   882  
   883  		return nil, nil
   884  	})
   885  	if overallError != nil {
   886  		return nil, overallError
   887  	}
   888  
   889  	return &emptypb.Empty{}, nil
   890  }
   891  
   892  // UpdateRevokedCertificate stores new revocation information about an
   893  // already-revoked certificate. It will only store this information if the
   894  // cert is already revoked, if the new revocation reason is `KeyCompromise`,
   895  // and if the revokedDate is identical to the current revokedDate.
   896  func (ssa *SQLStorageAuthority) UpdateRevokedCertificate(ctx context.Context, req *sapb.RevokeCertificateRequest) (*emptypb.Empty, error) {
   897  	if core.IsAnyNilOrZero(req.Serial, req.IssuerID, req.Date, req.Backdate, req.ShardIdx) {
   898  		return nil, errIncompleteRequest
   899  	}
   900  	if revocation.Reason(req.Reason) != revocation.KeyCompromise {
   901  		return nil, fmt.Errorf("cannot update revocation for any reason other than keyCompromise (1); got: %d", req.Reason)
   902  	}
   903  
   904  	_, overallError := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (any, error) {
   905  		thisUpdate := req.Date.AsTime()
   906  		revokedDate := req.Backdate.AsTime()
   907  
   908  		res, err := tx.ExecContext(ctx,
   909  			`UPDATE certificateStatus SET
   910  					revokedReason = ?,
   911  					ocspLastUpdated = ?
   912  				WHERE serial = ? AND status = ? AND revokedReason != ? AND revokedDate = ?`,
   913  			revocation.KeyCompromise,
   914  			thisUpdate,
   915  			req.Serial,
   916  			string(core.OCSPStatusRevoked),
   917  			revocation.KeyCompromise,
   918  			revokedDate,
   919  		)
   920  		if err != nil {
   921  			return nil, err
   922  		}
   923  		rows, err := res.RowsAffected()
   924  		if err != nil {
   925  			return nil, err
   926  		}
   927  		if rows == 0 {
   928  			// InternalServerError because we expected this certificate status to exist,
   929  			// to already be revoked for a different reason, and to have a matching date.
   930  			return nil, berrors.InternalServerError("no certificate with serial %s and revoked reason other than keyCompromise", req.Serial)
   931  		}
   932  
   933  		var rcm revokedCertModel
   934  		// Note: this query MUST be updated to enforce the same preconditions as
   935  		// the "UPDATE certificateStatus SET revokedReason..." above if this
   936  		// query ever becomes the first or only query in this transaction. We are
   937  		// currently relying on the query above to exit early if the certificate
   938  		// does not have an appropriate status and revocation reason.
   939  		err = tx.SelectOne(
   940  			ctx, &rcm, `SELECT * FROM revokedCertificates WHERE serial = ?`, req.Serial)
   941  		if db.IsNoRows(err) {
   942  			// TODO: Remove this fallback codepath once we know that all unexpired
   943  			// certs marked as revoked in the certificateStatus table have
   944  			// corresponding rows in the revokedCertificates table. That should be
   945  			// 90+ days after the RA starts sending ShardIdx in its
   946  			// RevokeCertificateRequest messages.
   947  			err = addRevokedCertificate(ctx, tx, req, revokedDate)
   948  			if err != nil {
   949  				return nil, err
   950  			}
   951  			return nil, nil
   952  		} else if err != nil {
   953  			return nil, fmt.Errorf("retrieving revoked certificate row: %w", err)
   954  		}
   955  
   956  		if rcm.ShardIdx != req.ShardIdx {
   957  			return nil, berrors.InternalServerError("mismatched shard index %d != %d", req.ShardIdx, rcm.ShardIdx)
   958  		}
   959  
   960  		rcm.RevokedReason = revocation.KeyCompromise
   961  		_, err = tx.Update(ctx, &rcm)
   962  		if err != nil {
   963  			return nil, fmt.Errorf("updating revoked certificate row: %w", err)
   964  		}
   965  
   966  		return nil, nil
   967  	})
   968  	if overallError != nil {
   969  		return nil, overallError
   970  	}
   971  
   972  	return &emptypb.Empty{}, nil
   973  }
   974  
   975  // AddBlockedKey adds a key hash to the blockedKeys table
   976  func (ssa *SQLStorageAuthority) AddBlockedKey(ctx context.Context, req *sapb.AddBlockedKeyRequest) (*emptypb.Empty, error) {
   977  	if core.IsAnyNilOrZero(req.KeyHash, req.Added, req.Source) {
   978  		return nil, errIncompleteRequest
   979  	}
   980  	sourceInt, ok := stringToSourceInt[req.Source]
   981  	if !ok {
   982  		return nil, errors.New("unknown source")
   983  	}
   984  	cols, qs := blockedKeysColumns, "?, ?, ?, ?"
   985  	vals := []any{
   986  		req.KeyHash,
   987  		req.Added.AsTime(),
   988  		sourceInt,
   989  		req.Comment,
   990  	}
   991  	if req.RevokedBy != 0 {
   992  		cols += ", revokedBy"
   993  		qs += ", ?"
   994  		vals = append(vals, req.RevokedBy)
   995  	}
   996  	_, err := ssa.dbMap.ExecContext(ctx,
   997  		fmt.Sprintf("INSERT INTO blockedKeys (%s) VALUES (%s)", cols, qs),
   998  		vals...,
   999  	)
  1000  	if err != nil {
  1001  		if db.IsDuplicate(err) {
  1002  			// Ignore duplicate inserts so multiple certs with the same key can
  1003  			// be revoked.
  1004  			return &emptypb.Empty{}, nil
  1005  		}
  1006  		return nil, err
  1007  	}
  1008  	return &emptypb.Empty{}, nil
  1009  }
  1010  
  1011  // Health implements the grpc.checker interface.
  1012  func (ssa *SQLStorageAuthority) Health(ctx context.Context) error {
  1013  	err := ssa.dbMap.SelectOne(ctx, new(int), "SELECT 1")
  1014  	if err != nil {
  1015  		return err
  1016  	}
  1017  
  1018  	err = ssa.SQLStorageAuthorityRO.Health(ctx)
  1019  	if err != nil {
  1020  		return err
  1021  	}
  1022  	return nil
  1023  }
  1024  
  1025  // LeaseCRLShard marks a single crlShards row as leased until the given time.
  1026  // If the request names a specific shard, this function will return an error
  1027  // if that shard is already leased. Otherwise, this function will return the
  1028  // index of the oldest shard for the given issuer.
  1029  func (ssa *SQLStorageAuthority) LeaseCRLShard(ctx context.Context, req *sapb.LeaseCRLShardRequest) (*sapb.LeaseCRLShardResponse, error) {
  1030  	if core.IsAnyNilOrZero(req.Until, req.IssuerNameID) {
  1031  		return nil, errIncompleteRequest
  1032  	}
  1033  	if req.Until.AsTime().Before(ssa.clk.Now()) {
  1034  		return nil, fmt.Errorf("lease timestamp must be in the future, got %q", req.Until.AsTime())
  1035  	}
  1036  
  1037  	if req.MinShardIdx == req.MaxShardIdx {
  1038  		return ssa.leaseSpecificCRLShard(ctx, req)
  1039  	}
  1040  
  1041  	return ssa.leaseOldestCRLShard(ctx, req)
  1042  }
  1043  
  1044  // leaseOldestCRLShard finds the oldest unleased crl shard for the given issuer
  1045  // and then leases it. Shards within the requested range which have never been
  1046  // leased or are previously-unknown indices are considered older than any other
  1047  // shard. It returns an error if all shards for the issuer are already leased.
  1048  func (ssa *SQLStorageAuthority) leaseOldestCRLShard(ctx context.Context, req *sapb.LeaseCRLShardRequest) (*sapb.LeaseCRLShardResponse, error) {
  1049  	shardIdx, err := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (any, error) {
  1050  		var shards []*crlShardModel
  1051  		_, err := tx.Select(
  1052  			ctx,
  1053  			&shards,
  1054  			`SELECT id, issuerID, idx, thisUpdate, nextUpdate, leasedUntil
  1055  				FROM crlShards
  1056  				WHERE issuerID = ?
  1057  				AND idx BETWEEN ? AND ?`,
  1058  			req.IssuerNameID, req.MinShardIdx, req.MaxShardIdx,
  1059  		)
  1060  		if err != nil {
  1061  			return -1, fmt.Errorf("selecting candidate shards: %w", err)
  1062  		}
  1063  
  1064  		// Determine which shard index we want to lease.
  1065  		var shardIdx int
  1066  		if len(shards) < (int(req.MaxShardIdx + 1 - req.MinShardIdx)) {
  1067  			// Some expected shards are missing (i.e. never-before-produced), so we
  1068  			// pick one at random.
  1069  			missing := make(map[int]struct{}, req.MaxShardIdx+1-req.MinShardIdx)
  1070  			for i := req.MinShardIdx; i <= req.MaxShardIdx; i++ {
  1071  				missing[int(i)] = struct{}{}
  1072  			}
  1073  			for _, shard := range shards {
  1074  				delete(missing, shard.Idx)
  1075  			}
  1076  			for idx := range missing {
  1077  				// Go map iteration is guaranteed to be in randomized key order.
  1078  				shardIdx = idx
  1079  				break
  1080  			}
  1081  
  1082  			_, err = tx.ExecContext(ctx,
  1083  				`INSERT INTO crlShards (issuerID, idx, leasedUntil)
  1084  					VALUES (?, ?, ?)`,
  1085  				req.IssuerNameID,
  1086  				shardIdx,
  1087  				req.Until.AsTime(),
  1088  			)
  1089  			if err != nil {
  1090  				return -1, fmt.Errorf("inserting selected shard: %w", err)
  1091  			}
  1092  		} else {
  1093  			// We got all the shards we expect, so we pick the oldest unleased shard.
  1094  			var oldest *crlShardModel
  1095  			for _, shard := range shards {
  1096  				if shard.LeasedUntil.After(ssa.clk.Now()) {
  1097  					continue
  1098  				}
  1099  				if oldest == nil ||
  1100  					(oldest.ThisUpdate != nil && shard.ThisUpdate == nil) ||
  1101  					(oldest.ThisUpdate != nil && shard.ThisUpdate.Before(*oldest.ThisUpdate)) {
  1102  					oldest = shard
  1103  				}
  1104  			}
  1105  			if oldest == nil {
  1106  				return -1, fmt.Errorf("issuer %d has no unleased shards in range %d-%d", req.IssuerNameID, req.MinShardIdx, req.MaxShardIdx)
  1107  			}
  1108  			shardIdx = oldest.Idx
  1109  
  1110  			res, err := tx.ExecContext(ctx,
  1111  				`UPDATE crlShards
  1112  					SET leasedUntil = ?
  1113  					WHERE issuerID = ?
  1114  					AND idx = ?
  1115  					AND leasedUntil = ?
  1116  					LIMIT 1`,
  1117  				req.Until.AsTime(),
  1118  				req.IssuerNameID,
  1119  				shardIdx,
  1120  				oldest.LeasedUntil,
  1121  			)
  1122  			if err != nil {
  1123  				return -1, fmt.Errorf("updating selected shard: %w", err)
  1124  			}
  1125  			rowsAffected, err := res.RowsAffected()
  1126  			if err != nil {
  1127  				return -1, fmt.Errorf("confirming update of selected shard: %w", err)
  1128  			}
  1129  			if rowsAffected != 1 {
  1130  				return -1, errors.New("failed to lease shard")
  1131  			}
  1132  		}
  1133  
  1134  		return shardIdx, err
  1135  	})
  1136  	if err != nil {
  1137  		return nil, fmt.Errorf("leasing oldest shard: %w", err)
  1138  	}
  1139  
  1140  	return &sapb.LeaseCRLShardResponse{
  1141  		IssuerNameID: req.IssuerNameID,
  1142  		ShardIdx:     int64(shardIdx.(int)),
  1143  	}, nil
  1144  }
  1145  
  1146  // leaseSpecificCRLShard attempts to lease the crl shard for the given issuer
  1147  // and shard index. It returns an error if the specified shard is already
  1148  // leased.
  1149  func (ssa *SQLStorageAuthority) leaseSpecificCRLShard(ctx context.Context, req *sapb.LeaseCRLShardRequest) (*sapb.LeaseCRLShardResponse, error) {
  1150  	if req.MinShardIdx != req.MaxShardIdx {
  1151  		return nil, fmt.Errorf("request must identify a single shard index: %d != %d", req.MinShardIdx, req.MaxShardIdx)
  1152  	}
  1153  
  1154  	_, err := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (any, error) {
  1155  		needToInsert := false
  1156  		var shardModel crlShardModel
  1157  		err := tx.SelectOne(ctx,
  1158  			&shardModel,
  1159  			`SELECT leasedUntil
  1160  			  FROM crlShards
  1161  				WHERE issuerID = ?
  1162  				AND idx = ?
  1163  				LIMIT 1`,
  1164  			req.IssuerNameID,
  1165  			req.MinShardIdx,
  1166  		)
  1167  		if db.IsNoRows(err) {
  1168  			needToInsert = true
  1169  		} else if err != nil {
  1170  			return nil, fmt.Errorf("selecting requested shard: %w", err)
  1171  		} else if shardModel.LeasedUntil.After(ssa.clk.Now()) {
  1172  			return nil, fmt.Errorf("shard %d for issuer %d already leased", req.MinShardIdx, req.IssuerNameID)
  1173  		}
  1174  
  1175  		if needToInsert {
  1176  			_, err = tx.ExecContext(ctx,
  1177  				`INSERT INTO crlShards (issuerID, idx, leasedUntil)
  1178  					VALUES (?, ?, ?)`,
  1179  				req.IssuerNameID,
  1180  				req.MinShardIdx,
  1181  				req.Until.AsTime(),
  1182  			)
  1183  			if err != nil {
  1184  				return nil, fmt.Errorf("inserting selected shard: %w", err)
  1185  			}
  1186  		} else {
  1187  			res, err := tx.ExecContext(ctx,
  1188  				`UPDATE crlShards
  1189  					SET leasedUntil = ?
  1190  					WHERE issuerID = ?
  1191  					AND idx = ?
  1192  					AND leasedUntil = ?
  1193  					LIMIT 1`,
  1194  				req.Until.AsTime(),
  1195  				req.IssuerNameID,
  1196  				req.MinShardIdx,
  1197  				shardModel.LeasedUntil,
  1198  			)
  1199  			if err != nil {
  1200  				return nil, fmt.Errorf("updating selected shard: %w", err)
  1201  			}
  1202  			rowsAffected, err := res.RowsAffected()
  1203  			if err != nil {
  1204  				return -1, fmt.Errorf("confirming update of selected shard: %w", err)
  1205  			}
  1206  			if rowsAffected != 1 {
  1207  				return -1, errors.New("failed to lease shard")
  1208  			}
  1209  		}
  1210  
  1211  		return nil, nil
  1212  	})
  1213  	if err != nil {
  1214  		return nil, fmt.Errorf("leasing specific shard: %w", err)
  1215  	}
  1216  
  1217  	return &sapb.LeaseCRLShardResponse{
  1218  		IssuerNameID: req.IssuerNameID,
  1219  		ShardIdx:     req.MinShardIdx,
  1220  	}, nil
  1221  }
  1222  
  1223  // UpdateCRLShard updates the thisUpdate and nextUpdate timestamps of a CRL
  1224  // shard. It rejects the update if it would cause the thisUpdate timestamp to
  1225  // move backwards, but if thisUpdate would stay the same (for instance, multiple
  1226  // CRL generations within a single second), it will succeed.
  1227  //
  1228  // It does *not* reject the update if the shard is no longer
  1229  // leased: although this would be unexpected (because the lease timestamp should
  1230  // be the same as the crl-updater's context expiration), it's not inherently a
  1231  // sign of an update that should be skipped. It does reject the update if the
  1232  // identified CRL shard does not exist in the database (it should exist, as
  1233  // rows are created if necessary when leased). It also sets the leasedUntil time
  1234  // to be equal to thisUpdate, to indicate that the shard is no longer leased.
  1235  func (ssa *SQLStorageAuthority) UpdateCRLShard(ctx context.Context, req *sapb.UpdateCRLShardRequest) (*emptypb.Empty, error) {
  1236  	if core.IsAnyNilOrZero(req.IssuerNameID, req.ThisUpdate) {
  1237  		return nil, errIncompleteRequest
  1238  	}
  1239  
  1240  	// Only set the nextUpdate if it's actually present in the request message.
  1241  	var nextUpdate *time.Time
  1242  	if req.NextUpdate != nil {
  1243  		nut := req.NextUpdate.AsTime()
  1244  		nextUpdate = &nut
  1245  	}
  1246  
  1247  	_, err := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (any, error) {
  1248  		res, err := tx.ExecContext(ctx,
  1249  			`UPDATE crlShards
  1250  				SET thisUpdate = ?, nextUpdate = ?, leasedUntil = ?
  1251  				WHERE issuerID = ?
  1252  				AND idx = ?
  1253  				AND (thisUpdate is NULL OR thisUpdate <= ?)
  1254  				LIMIT 1`,
  1255  			req.ThisUpdate.AsTime(),
  1256  			nextUpdate,
  1257  			req.ThisUpdate.AsTime(),
  1258  			req.IssuerNameID,
  1259  			req.ShardIdx,
  1260  			req.ThisUpdate.AsTime(),
  1261  		)
  1262  		if err != nil {
  1263  			return nil, err
  1264  		}
  1265  
  1266  		rowsAffected, err := res.RowsAffected()
  1267  		if err != nil {
  1268  			return nil, err
  1269  		}
  1270  		if rowsAffected == 0 {
  1271  			return nil, fmt.Errorf("unable to update shard %d for issuer %d; possibly because shard exists", req.ShardIdx, req.IssuerNameID)
  1272  		}
  1273  		if rowsAffected != 1 {
  1274  			return nil, errors.New("update affected unexpected number of rows")
  1275  		}
  1276  		return nil, nil
  1277  	})
  1278  	if err != nil {
  1279  		return nil, err
  1280  	}
  1281  
  1282  	return &emptypb.Empty{}, nil
  1283  }
  1284  
  1285  // PauseIdentifiers pauses a set of identifiers for the provided account. If an
  1286  // identifier is currently paused, this is a no-op. If an identifier was
  1287  // previously paused and unpaused, it will be repaused unless it was unpaused
  1288  // less than two weeks ago. The response will indicate how many identifiers were
  1289  // paused and how many were repaused. All work is accomplished in a transaction
  1290  // to limit possible race conditions.
  1291  func (ssa *SQLStorageAuthority) PauseIdentifiers(ctx context.Context, req *sapb.PauseRequest) (*sapb.PauseIdentifiersResponse, error) {
  1292  	if core.IsAnyNilOrZero(req.RegistrationID, req.Identifiers) {
  1293  		return nil, errIncompleteRequest
  1294  	}
  1295  
  1296  	// Marshal the identifier now that we've crossed the RPC boundary.
  1297  	idents, err := newIdentifierModelsFromPB(req.Identifiers)
  1298  	if err != nil {
  1299  		return nil, err
  1300  	}
  1301  
  1302  	response := &sapb.PauseIdentifiersResponse{}
  1303  	_, err = db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (any, error) {
  1304  		for _, ident := range idents {
  1305  			pauseError := func(op string, err error) error {
  1306  				return fmt.Errorf("while %s identifier %s for registration ID %d: %w",
  1307  					op, ident.Value, req.RegistrationID, err,
  1308  				)
  1309  			}
  1310  
  1311  			var entry pausedModel
  1312  			err := tx.SelectOne(ctx, &entry, `
  1313  			SELECT pausedAt, unpausedAt
  1314  			FROM paused
  1315  			WHERE
  1316  				registrationID = ? AND
  1317  				identifierType = ? AND
  1318  				identifierValue = ?`,
  1319  				req.RegistrationID,
  1320  				ident.Type,
  1321  				ident.Value,
  1322  			)
  1323  
  1324  			switch {
  1325  			case err != nil && !errors.Is(err, sql.ErrNoRows):
  1326  				// Error querying the database.
  1327  				return nil, pauseError("querying pause status for", err)
  1328  
  1329  			case err != nil && errors.Is(err, sql.ErrNoRows):
  1330  				// Not currently or previously paused, insert a new pause record.
  1331  				err = tx.Insert(ctx, &pausedModel{
  1332  					RegistrationID: req.RegistrationID,
  1333  					PausedAt:       ssa.clk.Now(),
  1334  					identifierModel: identifierModel{
  1335  						Type:  ident.Type,
  1336  						Value: ident.Value,
  1337  					},
  1338  				})
  1339  				if err != nil && !db.IsDuplicate(err) {
  1340  					return nil, pauseError("pausing", err)
  1341  				}
  1342  
  1343  				// Identifier successfully paused.
  1344  				response.Paused++
  1345  				continue
  1346  
  1347  			case entry.UnpausedAt == nil || entry.PausedAt.After(*entry.UnpausedAt):
  1348  				// Identifier is already paused.
  1349  				continue
  1350  
  1351  			case entry.UnpausedAt.After(ssa.clk.Now().Add(-14 * 24 * time.Hour)):
  1352  				// Previously unpaused less than two weeks ago, skip this identifier.
  1353  				continue
  1354  
  1355  			case entry.UnpausedAt.After(entry.PausedAt):
  1356  				// Previously paused (and unpaused), repause the identifier.
  1357  				_, err := tx.ExecContext(ctx, `
  1358  				UPDATE paused
  1359  				SET pausedAt = ?,
  1360  				    unpausedAt = NULL
  1361  				WHERE
  1362  					registrationID = ? AND
  1363  					identifierType = ? AND
  1364  					identifierValue = ? AND
  1365  					unpausedAt IS NOT NULL`,
  1366  					ssa.clk.Now(),
  1367  					req.RegistrationID,
  1368  					ident.Type,
  1369  					ident.Value,
  1370  				)
  1371  				if err != nil {
  1372  					return nil, pauseError("repausing", err)
  1373  				}
  1374  
  1375  				// Identifier successfully repaused.
  1376  				response.Repaused++
  1377  				continue
  1378  
  1379  			default:
  1380  				// This indicates a database state which should never occur.
  1381  				return nil, fmt.Errorf("impossible database state encountered while pausing identifier %s",
  1382  					ident.Value,
  1383  				)
  1384  			}
  1385  		}
  1386  		return nil, nil
  1387  	})
  1388  	if err != nil {
  1389  		// Error occurred during transaction.
  1390  		return nil, err
  1391  	}
  1392  	return response, nil
  1393  }
  1394  
  1395  // UnpauseAccount uses up to 5 iterations of UPDATE queries each with a LIMIT of
  1396  // 10,000 to unpause up to 50,000 identifiers and returns a count of identifiers
  1397  // unpaused. If the returned count is 50,000 there may be more paused identifiers.
  1398  func (ssa *SQLStorageAuthority) UnpauseAccount(ctx context.Context, req *sapb.RegistrationID) (*sapb.Count, error) {
  1399  	if core.IsAnyNilOrZero(req.Id) {
  1400  		return nil, errIncompleteRequest
  1401  	}
  1402  
  1403  	total := &sapb.Count{}
  1404  
  1405  	for range unpause.MaxBatches {
  1406  		result, err := ssa.dbMap.ExecContext(ctx, `
  1407  			UPDATE paused
  1408  			SET unpausedAt = ?
  1409  			WHERE
  1410  				registrationID = ? AND
  1411  				unpausedAt IS NULL
  1412  			LIMIT ?`,
  1413  			ssa.clk.Now(),
  1414  			req.Id,
  1415  			unpause.BatchSize,
  1416  		)
  1417  		if err != nil {
  1418  			return nil, err
  1419  		}
  1420  
  1421  		rowsAffected, err := result.RowsAffected()
  1422  		if err != nil {
  1423  			return nil, err
  1424  		}
  1425  
  1426  		total.Count += rowsAffected
  1427  		if rowsAffected < unpause.BatchSize {
  1428  			// Fewer than batchSize rows were updated, so we're done.
  1429  			break
  1430  		}
  1431  	}
  1432  
  1433  	return total, nil
  1434  }
  1435  
  1436  // AddRateLimitOverride adds a rate limit override to the database. If the
  1437  // override already exists, it will be updated. If the override does not exist,
  1438  // it will be inserted and enabled. If the override exists but has been
  1439  // disabled, it will be updated but not be re-enabled. The status of the
  1440  // override is returned in Enabled field of the response. To re-enable an
  1441  // override, use the EnableRateLimitOverride method.
  1442  func (ssa *SQLStorageAuthority) AddRateLimitOverride(ctx context.Context, req *sapb.AddRateLimitOverrideRequest) (*sapb.AddRateLimitOverrideResponse, error) {
  1443  	if core.IsAnyNilOrZero(req, req.Override, req.Override.LimitEnum, req.Override.BucketKey, req.Override.Count, req.Override.Burst, req.Override.Period, req.Override.Comment) {
  1444  		return nil, errIncompleteRequest
  1445  	}
  1446  
  1447  	var inserted bool
  1448  	var enabled bool
  1449  	now := ssa.clk.Now()
  1450  
  1451  	_, err := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (any, error) {
  1452  		var alreadyEnabled bool
  1453  		err := tx.SelectOne(ctx, &alreadyEnabled, `
  1454  			SELECT enabled
  1455  			  FROM overrides
  1456  			 WHERE limitEnum = ? AND
  1457  			       bucketKey = ?`,
  1458  			req.Override.LimitEnum,
  1459  			req.Override.BucketKey,
  1460  		)
  1461  
  1462  		switch {
  1463  		case err != nil && !db.IsNoRows(err):
  1464  			// Error querying the database.
  1465  			return nil, fmt.Errorf("querying override for rate limit %d and bucket key %s: %w",
  1466  				req.Override.LimitEnum,
  1467  				req.Override.BucketKey,
  1468  				err,
  1469  			)
  1470  
  1471  		case db.IsNoRows(err):
  1472  			// Insert a new overrides row.
  1473  			new := overrideModelForPB(req.Override, now, true)
  1474  			err = tx.Insert(ctx, &new)
  1475  			if err != nil {
  1476  				return nil, fmt.Errorf("inserting override for rate limit %d and bucket key %s: %w",
  1477  					req.Override.LimitEnum,
  1478  					req.Override.BucketKey,
  1479  					err,
  1480  				)
  1481  			}
  1482  			inserted = true
  1483  			enabled = true
  1484  
  1485  		default:
  1486  			// Update the existing overrides row.
  1487  			updated := overrideModelForPB(req.Override, now, alreadyEnabled)
  1488  			_, err = tx.Update(ctx, &updated)
  1489  			if err != nil {
  1490  				return nil, fmt.Errorf("updating override for rate limit %d and bucket key %s override: %w",
  1491  					req.Override.LimitEnum,
  1492  					req.Override.BucketKey,
  1493  					err,
  1494  				)
  1495  			}
  1496  			inserted = false
  1497  			enabled = alreadyEnabled
  1498  		}
  1499  		return nil, nil
  1500  	})
  1501  	if err != nil {
  1502  		// Error occurred during transaction.
  1503  		return nil, err
  1504  	}
  1505  	return &sapb.AddRateLimitOverrideResponse{Inserted: inserted, Enabled: enabled}, nil
  1506  }
  1507  
  1508  // setRateLimitOverride sets the enabled field of a rate limit override to the
  1509  // provided value and updates the updatedAt column. If the override does not
  1510  // exist, a NotFoundError is returned. If the override exists but is already in
  1511  // the requested state, this is a no-op.
  1512  func (ssa *SQLStorageAuthority) setRateLimitOverride(ctx context.Context, limitEnum int64, bucketKey string, enabled bool) (*emptypb.Empty, error) {
  1513  	overrideColumnsList, err := ssa.dbMap.ColumnsForModel(overrideModel{})
  1514  	if err != nil {
  1515  		// This should never happen, the model is registered at init time.
  1516  		return nil, fmt.Errorf("getting columns for override model: %w", err)
  1517  	}
  1518  	overrideColumns := strings.Join(overrideColumnsList, ", ")
  1519  	_, err = db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (any, error) {
  1520  		var existing overrideModel
  1521  		err := tx.SelectOne(ctx, &existing,
  1522  			// Use SELECT FOR UPDATE to both verify the row exists and lock it
  1523  			// for the duration of the transaction.
  1524  			`SELECT `+overrideColumns+` FROM overrides
  1525  			 WHERE limitEnum = ? AND
  1526  			       bucketKey = ?
  1527  			 FOR UPDATE`,
  1528  			limitEnum,
  1529  			bucketKey,
  1530  		)
  1531  		if err != nil {
  1532  			if db.IsNoRows(err) {
  1533  				return nil, berrors.NotFoundError(
  1534  					"no rate limit override found for limit %d and bucket key %s",
  1535  					limitEnum,
  1536  					bucketKey,
  1537  				)
  1538  			}
  1539  			return nil, fmt.Errorf("querying status of override for rate limit %d and bucket key %s: %w",
  1540  				limitEnum,
  1541  				bucketKey,
  1542  				err,
  1543  			)
  1544  		}
  1545  
  1546  		if existing.Enabled == enabled {
  1547  			// No-op
  1548  			return nil, nil
  1549  		}
  1550  
  1551  		// Update the existing overrides row.
  1552  		updated := existing
  1553  		updated.Enabled = enabled
  1554  		updated.UpdatedAt = ssa.clk.Now()
  1555  
  1556  		_, err = tx.Update(ctx, &updated)
  1557  		if err != nil {
  1558  			return nil, fmt.Errorf("updating status of override for rate limit %d and bucket key %s to %t: %w",
  1559  				limitEnum,
  1560  				bucketKey,
  1561  				enabled,
  1562  				err,
  1563  			)
  1564  		}
  1565  		return nil, nil
  1566  	})
  1567  	if err != nil {
  1568  		return nil, err
  1569  	}
  1570  	return &emptypb.Empty{}, nil
  1571  }
  1572  
  1573  // DisableRateLimitOverride disables a rate limit override. If the override does
  1574  // not exist, a NotFoundError is returned. If the override exists but is already
  1575  // disabled, this is a no-op.
  1576  func (ssa *SQLStorageAuthority) DisableRateLimitOverride(ctx context.Context, req *sapb.DisableRateLimitOverrideRequest) (*emptypb.Empty, error) {
  1577  	if core.IsAnyNilOrZero(req, req.LimitEnum, req.BucketKey) {
  1578  		return nil, errIncompleteRequest
  1579  	}
  1580  	return ssa.setRateLimitOverride(ctx, req.LimitEnum, req.BucketKey, false)
  1581  }
  1582  
  1583  // EnableRateLimitOverride enables a rate limit override. If the override does
  1584  // not exist, a NotFoundError is returned. If the override exists but is already
  1585  // enabled, this is a no-op.
  1586  func (ssa *SQLStorageAuthority) EnableRateLimitOverride(ctx context.Context, req *sapb.EnableRateLimitOverrideRequest) (*emptypb.Empty, error) {
  1587  	if core.IsAnyNilOrZero(req, req.LimitEnum, req.BucketKey) {
  1588  		return nil, errIncompleteRequest
  1589  	}
  1590  	return ssa.setRateLimitOverride(ctx, req.LimitEnum, req.BucketKey, true)
  1591  }