...

Source file src/github.com/letsencrypt/boulder/sa/sa.go

Documentation: github.com/letsencrypt/boulder/sa

     1  package sa
     2  
     3  import (
     4  	"context"
     5  	"crypto/x509"
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"strings"
    10  	"time"
    11  
    12  	"github.com/jmhodges/clock"
    13  	"github.com/prometheus/client_golang/prometheus"
    14  	"golang.org/x/crypto/ocsp"
    15  	"google.golang.org/protobuf/types/known/emptypb"
    16  	"google.golang.org/protobuf/types/known/timestamppb"
    17  
    18  	"github.com/letsencrypt/boulder/core"
    19  	corepb "github.com/letsencrypt/boulder/core/proto"
    20  	"github.com/letsencrypt/boulder/db"
    21  	berrors "github.com/letsencrypt/boulder/errors"
    22  	bgrpc "github.com/letsencrypt/boulder/grpc"
    23  	blog "github.com/letsencrypt/boulder/log"
    24  	"github.com/letsencrypt/boulder/revocation"
    25  	sapb "github.com/letsencrypt/boulder/sa/proto"
    26  )
    27  
    28  var (
    29  	errIncompleteRequest = errors.New("incomplete gRPC request message")
    30  )
    31  
    32  // SQLStorageAuthority defines a Storage Authority.
    33  //
    34  // Note that although SQLStorageAuthority does have methods wrapping all of the
    35  // read-only methods provided by the SQLStorageAuthorityRO, those wrapper
    36  // implementations are in saro.go, next to the real implementations.
    37  type SQLStorageAuthority struct {
    38  	sapb.UnimplementedStorageAuthorityServer
    39  	*SQLStorageAuthorityRO
    40  
    41  	dbMap *db.WrappedMap
    42  
    43  	// rateLimitWriteErrors is a Counter for the number of times
    44  	// a ratelimit update transaction failed during AddCertificate request
    45  	// processing. We do not fail the overall AddCertificate call when ratelimit
    46  	// transactions fail and so use this stat to maintain visibility into the rate
    47  	// this occurs.
    48  	rateLimitWriteErrors prometheus.Counter
    49  }
    50  
    51  // NewSQLStorageAuthorityWrapping provides persistence using a SQL backend for
    52  // Boulder. It takes a read-only storage authority to wrap, which is useful if
    53  // you are constructing both types of implementations and want to share
    54  // read-only database connections between them.
    55  func NewSQLStorageAuthorityWrapping(
    56  	ssaro *SQLStorageAuthorityRO,
    57  	dbMap *db.WrappedMap,
    58  	stats prometheus.Registerer,
    59  ) (*SQLStorageAuthority, error) {
    60  	rateLimitWriteErrors := prometheus.NewCounter(prometheus.CounterOpts{
    61  		Name: "rate_limit_write_errors",
    62  		Help: "number of failed ratelimit update transactions during AddCertificate",
    63  	})
    64  	stats.MustRegister(rateLimitWriteErrors)
    65  
    66  	ssa := &SQLStorageAuthority{
    67  		SQLStorageAuthorityRO: ssaro,
    68  		dbMap:                 dbMap,
    69  		rateLimitWriteErrors:  rateLimitWriteErrors,
    70  	}
    71  
    72  	return ssa, nil
    73  }
    74  
    75  // NewSQLStorageAuthority provides persistence using a SQL backend for
    76  // Boulder. It constructs its own read-only storage authority to wrap.
    77  func NewSQLStorageAuthority(
    78  	dbMap *db.WrappedMap,
    79  	dbReadOnlyMap *db.WrappedMap,
    80  	dbIncidentsMap *db.WrappedMap,
    81  	parallelismPerRPC int,
    82  	lagFactor time.Duration,
    83  	clk clock.Clock,
    84  	logger blog.Logger,
    85  	stats prometheus.Registerer,
    86  ) (*SQLStorageAuthority, error) {
    87  	ssaro, err := NewSQLStorageAuthorityRO(
    88  		dbReadOnlyMap, dbIncidentsMap, stats, parallelismPerRPC, lagFactor, clk, logger)
    89  	if err != nil {
    90  		return nil, err
    91  	}
    92  
    93  	return NewSQLStorageAuthorityWrapping(ssaro, dbMap, stats)
    94  }
    95  
    96  // NewRegistration stores a new Registration
    97  func (ssa *SQLStorageAuthority) NewRegistration(ctx context.Context, req *corepb.Registration) (*corepb.Registration, error) {
    98  	if len(req.Key) == 0 || len(req.InitialIP) == 0 {
    99  		return nil, errIncompleteRequest
   100  	}
   101  
   102  	reg, err := registrationPbToModel(req)
   103  	if err != nil {
   104  		return nil, err
   105  	}
   106  
   107  	reg.CreatedAt = ssa.clk.Now()
   108  
   109  	err = ssa.dbMap.Insert(ctx, reg)
   110  	if err != nil {
   111  		if db.IsDuplicate(err) {
   112  			// duplicate entry error can only happen when jwk_sha256 collides, indicate
   113  			// to caller that the provided key is already in use
   114  			return nil, berrors.DuplicateError("key is already in use for a different account")
   115  		}
   116  		return nil, err
   117  	}
   118  	return registrationModelToPb(reg)
   119  }
   120  
   121  // UpdateRegistration stores an updated Registration
   122  func (ssa *SQLStorageAuthority) UpdateRegistration(ctx context.Context, req *corepb.Registration) (*emptypb.Empty, error) {
   123  	if req == nil || req.Id == 0 || len(req.Key) == 0 || len(req.InitialIP) == 0 {
   124  		return nil, errIncompleteRequest
   125  	}
   126  
   127  	curr, err := selectRegistration(ctx, ssa.dbMap, "id", req.Id)
   128  	if err != nil {
   129  		if db.IsNoRows(err) {
   130  			return nil, berrors.NotFoundError("registration with ID '%d' not found", req.Id)
   131  		}
   132  		return nil, err
   133  	}
   134  
   135  	update, err := registrationPbToModel(req)
   136  	if err != nil {
   137  		return nil, err
   138  	}
   139  
   140  	// Copy the existing registration model's LockCol to the new updated
   141  	// registration model's LockCol
   142  	update.LockCol = curr.LockCol
   143  	n, err := ssa.dbMap.Update(ctx, update)
   144  	if err != nil {
   145  		if db.IsDuplicate(err) {
   146  			// duplicate entry error can only happen when jwk_sha256 collides, indicate
   147  			// to caller that the provided key is already in use
   148  			return nil, berrors.DuplicateError("key is already in use for a different account")
   149  		}
   150  		return nil, err
   151  	}
   152  	if n == 0 {
   153  		return nil, berrors.NotFoundError("registration with ID '%d' not found", req.Id)
   154  	}
   155  
   156  	return &emptypb.Empty{}, nil
   157  }
   158  
   159  // AddSerial writes a record of a serial number generation to the DB.
   160  func (ssa *SQLStorageAuthority) AddSerial(ctx context.Context, req *sapb.AddSerialRequest) (*emptypb.Empty, error) {
   161  	if req.Serial == "" || req.RegID == 0 || req.CreatedNS == 0 || req.ExpiresNS == 0 {
   162  		return nil, errIncompleteRequest
   163  	}
   164  	err := ssa.dbMap.Insert(ctx, &recordedSerialModel{
   165  		Serial:         req.Serial,
   166  		RegistrationID: req.RegID,
   167  		Created:        time.Unix(0, req.CreatedNS),
   168  		Expires:        time.Unix(0, req.ExpiresNS),
   169  	})
   170  	if err != nil {
   171  		return nil, err
   172  	}
   173  	return &emptypb.Empty{}, nil
   174  }
   175  
   176  // SetCertificateStatusReady changes a serial's OCSP status from core.OCSPStatusNotReady to core.OCSPStatusGood.
   177  // Called when precertificate issuance succeeds. returns an error if the serial doesn't have status core.OCSPStatusNotReady.
   178  func (ssa *SQLStorageAuthority) SetCertificateStatusReady(ctx context.Context, req *sapb.Serial) (*emptypb.Empty, error) {
   179  	res, err := ssa.dbMap.ExecContext(ctx,
   180  		`UPDATE certificateStatus
   181  		 SET status = ?
   182  		 WHERE status = ? AND
   183  		       serial = ?`,
   184  		string(core.OCSPStatusGood),
   185  		string(core.OCSPStatusNotReady),
   186  		req.Serial,
   187  	)
   188  	if err != nil {
   189  		return nil, err
   190  	}
   191  	rows, err := res.RowsAffected()
   192  	if err != nil {
   193  		return nil, err
   194  	}
   195  	if rows == 0 {
   196  		return nil, errors.New("failed to set certificate status to ready")
   197  	}
   198  
   199  	return &emptypb.Empty{}, nil
   200  }
   201  
   202  // AddPrecertificate writes a record of a precertificate generation to the DB.
   203  // Note: this is not idempotent: it does not protect against inserting the same
   204  // certificate multiple times. Calling code needs to first insert the cert's
   205  // serial into the Serials table to ensure uniqueness.
   206  func (ssa *SQLStorageAuthority) AddPrecertificate(ctx context.Context, req *sapb.AddCertificateRequest) (*emptypb.Empty, error) {
   207  	if len(req.Der) == 0 || req.RegID == 0 || req.IssuedNS == 0 || req.IssuerNameID == 0 {
   208  		return nil, errIncompleteRequest
   209  	}
   210  	parsed, err := x509.ParseCertificate(req.Der)
   211  	if err != nil {
   212  		return nil, err
   213  	}
   214  	serialHex := core.SerialToString(parsed.SerialNumber)
   215  
   216  	preCertModel := &precertificateModel{
   217  		Serial:         serialHex,
   218  		RegistrationID: req.RegID,
   219  		DER:            req.Der,
   220  		Issued:         time.Unix(0, req.IssuedNS),
   221  		Expires:        parsed.NotAfter,
   222  	}
   223  
   224  	_, overallError := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (interface{}, error) {
   225  		// Select to see if precert exists
   226  		var row struct {
   227  			Count int64
   228  		}
   229  		err := tx.SelectOne(ctx, &row, "SELECT COUNT(*) as count FROM precertificates WHERE serial=?", serialHex)
   230  		if err != nil {
   231  			return nil, err
   232  		}
   233  		if row.Count > 0 {
   234  			return nil, berrors.DuplicateError("cannot add a duplicate cert")
   235  		}
   236  
   237  		err = tx.Insert(ctx, preCertModel)
   238  		if err != nil {
   239  			return nil, err
   240  		}
   241  
   242  		status := core.OCSPStatusGood
   243  		if req.OcspNotReady {
   244  			status = core.OCSPStatusNotReady
   245  		}
   246  		cs := &core.CertificateStatus{
   247  			Serial:                serialHex,
   248  			Status:                status,
   249  			OCSPLastUpdated:       ssa.clk.Now(),
   250  			RevokedDate:           time.Time{},
   251  			RevokedReason:         0,
   252  			LastExpirationNagSent: time.Time{},
   253  			NotAfter:              parsed.NotAfter,
   254  			IsExpired:             false,
   255  			IssuerNameID:          req.IssuerNameID,
   256  		}
   257  		err = ssa.dbMap.Insert(ctx, cs)
   258  		if err != nil {
   259  			return nil, err
   260  		}
   261  
   262  		// NOTE(@cpu): When we collect up names to check if an FQDN set exists (e.g.
   263  		// that it is a renewal) we use just the DNSNames from the certificate and
   264  		// ignore the Subject Common Name (if any). This is a safe assumption because
   265  		// if a certificate we issued were to have a Subj. CN not present as a SAN it
   266  		// would be a misissuance and miscalculating whether the cert is a renewal or
   267  		// not for the purpose of rate limiting is the least of our troubles.
   268  		isRenewal, err := ssa.checkFQDNSetExists(
   269  			ctx,
   270  			tx.SelectOne,
   271  			parsed.DNSNames)
   272  		if err != nil {
   273  			return nil, err
   274  		}
   275  
   276  		err = addIssuedNames(ctx, tx, parsed, isRenewal)
   277  		if err != nil {
   278  			return nil, err
   279  		}
   280  
   281  		err = addKeyHash(ctx, tx, parsed)
   282  		if err != nil {
   283  			return nil, err
   284  		}
   285  
   286  		return nil, nil
   287  	})
   288  	if overallError != nil {
   289  		return nil, overallError
   290  	}
   291  
   292  	return &emptypb.Empty{}, nil
   293  }
   294  
   295  // AddCertificate stores an issued certificate, returning an error if it is a
   296  // duplicate or if any other failure occurs.
   297  func (ssa *SQLStorageAuthority) AddCertificate(ctx context.Context, req *sapb.AddCertificateRequest) (*emptypb.Empty, error) {
   298  	if len(req.Der) == 0 || req.RegID == 0 || req.IssuedNS == 0 {
   299  		return nil, errIncompleteRequest
   300  	}
   301  	parsedCertificate, err := x509.ParseCertificate(req.Der)
   302  	if err != nil {
   303  		return nil, err
   304  	}
   305  	digest := core.Fingerprint256(req.Der)
   306  	serial := core.SerialToString(parsedCertificate.SerialNumber)
   307  
   308  	cert := &core.Certificate{
   309  		RegistrationID: req.RegID,
   310  		Serial:         serial,
   311  		Digest:         digest,
   312  		DER:            req.Der,
   313  		Issued:         time.Unix(0, req.IssuedNS),
   314  		Expires:        parsedCertificate.NotAfter,
   315  	}
   316  
   317  	isRenewalRaw, overallError := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (interface{}, error) {
   318  		// Select to see if cert exists
   319  		var row struct {
   320  			Count int64
   321  		}
   322  		err := tx.SelectOne(ctx, &row, "SELECT COUNT(*) as count FROM certificates WHERE serial=?", serial)
   323  		if err != nil {
   324  			return nil, err
   325  		}
   326  		if row.Count > 0 {
   327  			return nil, berrors.DuplicateError("cannot add a duplicate cert")
   328  		}
   329  
   330  		// Save the final certificate
   331  		err = tx.Insert(ctx, cert)
   332  		if err != nil {
   333  			return nil, err
   334  		}
   335  
   336  		// NOTE(@cpu): When we collect up names to check if an FQDN set exists (e.g.
   337  		// that it is a renewal) we use just the DNSNames from the certificate and
   338  		// ignore the Subject Common Name (if any). This is a safe assumption because
   339  		// if a certificate we issued were to have a Subj. CN not present as a SAN it
   340  		// would be a misissuance and miscalculating whether the cert is a renewal or
   341  		// not for the purpose of rate limiting is the least of our troubles.
   342  		isRenewal, err := ssa.checkFQDNSetExists(
   343  			ctx,
   344  			tx.SelectOne,
   345  			parsedCertificate.DNSNames)
   346  		if err != nil {
   347  			return nil, err
   348  		}
   349  
   350  		return isRenewal, err
   351  	})
   352  	if overallError != nil {
   353  		return nil, overallError
   354  	}
   355  
   356  	// Recast the interface{} return from db.WithTransaction as a bool, returning
   357  	// an error if we can't.
   358  	var isRenewal bool
   359  	if boolVal, ok := isRenewalRaw.(bool); !ok {
   360  		return nil, fmt.Errorf(
   361  			"AddCertificate db.WithTransaction returned %T out var, expected bool",
   362  			isRenewalRaw)
   363  	} else {
   364  		isRenewal = boolVal
   365  	}
   366  
   367  	// In a separate transaction perform the work required to update tables used
   368  	// for rate limits. Since the effects of failing these writes is slight
   369  	// miscalculation of rate limits we choose to not fail the AddCertificate
   370  	// operation if the rate limit update transaction fails.
   371  	_, rlTransactionErr := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (interface{}, error) {
   372  		// Add to the rate limit table, but only for new certificates. Renewals
   373  		// don't count against the certificatesPerName limit.
   374  		if !isRenewal {
   375  			timeToTheHour := parsedCertificate.NotBefore.Round(time.Hour)
   376  			err := ssa.addCertificatesPerName(ctx, tx, parsedCertificate.DNSNames, timeToTheHour)
   377  			if err != nil {
   378  				return nil, err
   379  			}
   380  		}
   381  
   382  		// Update the FQDN sets now that there is a final certificate to ensure rate
   383  		// limits are calculated correctly.
   384  		err = addFQDNSet(
   385  			ctx,
   386  			tx,
   387  			parsedCertificate.DNSNames,
   388  			core.SerialToString(parsedCertificate.SerialNumber),
   389  			parsedCertificate.NotBefore,
   390  			parsedCertificate.NotAfter,
   391  		)
   392  		if err != nil {
   393  			return nil, err
   394  		}
   395  
   396  		return nil, nil
   397  	})
   398  	// If the ratelimit transaction failed increment a stat and log a warning
   399  	// but don't return an error from AddCertificate.
   400  	if rlTransactionErr != nil {
   401  		ssa.rateLimitWriteErrors.Inc()
   402  		ssa.log.AuditErrf("failed AddCertificate ratelimit update transaction: %v", rlTransactionErr)
   403  	}
   404  
   405  	return &emptypb.Empty{}, nil
   406  }
   407  
   408  // DeactivateRegistration deactivates a currently valid registration
   409  func (ssa *SQLStorageAuthority) DeactivateRegistration(ctx context.Context, req *sapb.RegistrationID) (*emptypb.Empty, error) {
   410  	if req == nil || req.Id == 0 {
   411  		return nil, errIncompleteRequest
   412  	}
   413  	_, err := ssa.dbMap.ExecContext(ctx,
   414  		"UPDATE registrations SET status = ? WHERE status = ? AND id = ?",
   415  		string(core.StatusDeactivated),
   416  		string(core.StatusValid),
   417  		req.Id,
   418  	)
   419  	if err != nil {
   420  		return nil, err
   421  	}
   422  	return &emptypb.Empty{}, nil
   423  }
   424  
   425  // DeactivateAuthorization2 deactivates a currently valid or pending authorization.
   426  func (ssa *SQLStorageAuthority) DeactivateAuthorization2(ctx context.Context, req *sapb.AuthorizationID2) (*emptypb.Empty, error) {
   427  	if req.Id == 0 {
   428  		return nil, errIncompleteRequest
   429  	}
   430  
   431  	_, err := ssa.dbMap.ExecContext(ctx,
   432  		`UPDATE authz2 SET status = :deactivated WHERE id = :id and status IN (:valid,:pending)`,
   433  		map[string]interface{}{
   434  			"deactivated": statusUint(core.StatusDeactivated),
   435  			"id":          req.Id,
   436  			"valid":       statusUint(core.StatusValid),
   437  			"pending":     statusUint(core.StatusPending),
   438  		},
   439  	)
   440  	if err != nil {
   441  		return nil, err
   442  	}
   443  	return &emptypb.Empty{}, nil
   444  }
   445  
   446  // NewOrderAndAuthzs adds the given authorizations to the database, adds their
   447  // autogenerated IDs to the given order, and then adds the order to the db.
   448  // This is done inside a single transaction to prevent situations where new
   449  // authorizations are created, but then their corresponding order is never
   450  // created, leading to "invisible" pending authorizations.
   451  func (ssa *SQLStorageAuthority) NewOrderAndAuthzs(ctx context.Context, req *sapb.NewOrderAndAuthzsRequest) (*corepb.Order, error) {
   452  	if req.NewOrder == nil {
   453  		return nil, errIncompleteRequest
   454  	}
   455  
   456  	output, err := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (interface{}, error) {
   457  		// First, insert all of the new authorizations and record their IDs.
   458  		newAuthzIDs := make([]int64, 0)
   459  		if len(req.NewAuthzs) != 0 {
   460  			inserter, err := db.NewMultiInserter("authz2", strings.Split(authzFields, ", "), "id")
   461  			if err != nil {
   462  				return nil, err
   463  			}
   464  			for _, authz := range req.NewAuthzs {
   465  				if authz.Status != string(core.StatusPending) {
   466  					return nil, berrors.InternalServerError("authorization must be pending")
   467  				}
   468  				am, err := authzPBToModel(authz)
   469  				if err != nil {
   470  					return nil, err
   471  				}
   472  				err = inserter.Add([]interface{}{
   473  					am.ID,
   474  					am.IdentifierType,
   475  					am.IdentifierValue,
   476  					am.RegistrationID,
   477  					statusToUint[core.StatusPending],
   478  					am.Expires,
   479  					am.Challenges,
   480  					nil,
   481  					nil,
   482  					am.Token,
   483  					nil,
   484  					nil,
   485  				})
   486  				if err != nil {
   487  					return nil, err
   488  				}
   489  			}
   490  			newAuthzIDs, err = inserter.Insert(ctx, tx)
   491  			if err != nil {
   492  				return nil, err
   493  			}
   494  		}
   495  
   496  		// Second, insert the new order.
   497  		order := &orderModel{
   498  			RegistrationID: req.NewOrder.RegistrationID,
   499  			Expires:        time.Unix(0, req.NewOrder.ExpiresNS),
   500  			Created:        ssa.clk.Now(),
   501  		}
   502  		err := tx.Insert(ctx, order)
   503  		if err != nil {
   504  			return nil, err
   505  		}
   506  
   507  		// Third, insert all of the orderToAuthz relations.
   508  		inserter, err := db.NewMultiInserter("orderToAuthz2", []string{"orderID", "authzID"}, "")
   509  		if err != nil {
   510  			return nil, err
   511  		}
   512  		for _, id := range req.NewOrder.V2Authorizations {
   513  			err = inserter.Add([]interface{}{order.ID, id})
   514  			if err != nil {
   515  				return nil, err
   516  			}
   517  		}
   518  		for _, id := range newAuthzIDs {
   519  			err = inserter.Add([]interface{}{order.ID, id})
   520  			if err != nil {
   521  				return nil, err
   522  			}
   523  		}
   524  		_, err = inserter.Insert(ctx, tx)
   525  		if err != nil {
   526  			return nil, err
   527  		}
   528  
   529  		// Fourth, insert all of the requestedNames.
   530  		inserter, err = db.NewMultiInserter("requestedNames", []string{"orderID", "reversedName"}, "")
   531  		if err != nil {
   532  			return nil, err
   533  		}
   534  		for _, name := range req.NewOrder.Names {
   535  			err = inserter.Add([]interface{}{order.ID, ReverseName(name)})
   536  			if err != nil {
   537  				return nil, err
   538  			}
   539  		}
   540  		_, err = inserter.Insert(ctx, tx)
   541  		if err != nil {
   542  			return nil, err
   543  		}
   544  
   545  		// Fifth, insert the FQDNSet entry for the order.
   546  		err = addOrderFQDNSet(ctx, tx, req.NewOrder.Names, order.ID, order.RegistrationID, order.Expires)
   547  		if err != nil {
   548  			return nil, err
   549  		}
   550  
   551  		// Finally, build the overall Order PB.
   552  		res := &corepb.Order{
   553  			// ID and Created were auto-populated on the order model when it was inserted.
   554  			Id:        order.ID,
   555  			CreatedNS: order.Created.UnixNano(),
   556  			Created:   timestamppb.New(order.Created),
   557  			// These are carried over from the original request unchanged.
   558  			RegistrationID: req.NewOrder.RegistrationID,
   559  			ExpiresNS:      req.NewOrder.ExpiresNS,
   560  			Expires:        timestamppb.New(time.Unix(0, req.NewOrder.ExpiresNS)),
   561  			Names:          req.NewOrder.Names,
   562  			// Have to combine the already-associated and newly-reacted authzs.
   563  			V2Authorizations: append(req.NewOrder.V2Authorizations, newAuthzIDs...),
   564  			// A new order is never processing because it can't be finalized yet.
   565  			BeganProcessing: false,
   566  		}
   567  
   568  		// Calculate the order status before returning it. Since it may have reused
   569  		// all valid authorizations the order may be "born" in a ready status.
   570  		status, err := statusForOrder(ctx, tx, res, ssa.clk.Now())
   571  		if err != nil {
   572  			return nil, err
   573  		}
   574  		res.Status = status
   575  
   576  		return res, nil
   577  	})
   578  	if err != nil {
   579  		return nil, err
   580  	}
   581  
   582  	order, ok := output.(*corepb.Order)
   583  	if !ok {
   584  		return nil, fmt.Errorf("casting error in NewOrderAndAuthzs")
   585  	}
   586  
   587  	// Increment the order creation count
   588  	err = addNewOrdersRateLimit(ctx, ssa.dbMap, req.NewOrder.RegistrationID, ssa.clk.Now().Truncate(time.Minute))
   589  	if err != nil {
   590  		return nil, err
   591  	}
   592  
   593  	return order, nil
   594  }
   595  
   596  // SetOrderProcessing updates an order from pending status to processing
   597  // status by updating the `beganProcessing` field of the corresponding
   598  // Order table row in the DB.
   599  func (ssa *SQLStorageAuthority) SetOrderProcessing(ctx context.Context, req *sapb.OrderRequest) (*emptypb.Empty, error) {
   600  	if req.Id == 0 {
   601  		return nil, errIncompleteRequest
   602  	}
   603  	_, overallError := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (interface{}, error) {
   604  		result, err := tx.ExecContext(ctx, `
   605  		UPDATE orders
   606  		SET beganProcessing = ?
   607  		WHERE id = ?
   608  		AND beganProcessing = ?`,
   609  			true,
   610  			req.Id,
   611  			false)
   612  		if err != nil {
   613  			return nil, berrors.InternalServerError("error updating order to beganProcessing status")
   614  		}
   615  
   616  		n, err := result.RowsAffected()
   617  		if err != nil || n == 0 {
   618  			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.")
   619  		}
   620  
   621  		return nil, nil
   622  	})
   623  	if overallError != nil {
   624  		return nil, overallError
   625  	}
   626  	return &emptypb.Empty{}, nil
   627  }
   628  
   629  // SetOrderError updates a provided Order's error field.
   630  func (ssa *SQLStorageAuthority) SetOrderError(ctx context.Context, req *sapb.SetOrderErrorRequest) (*emptypb.Empty, error) {
   631  	if req.Id == 0 || req.Error == nil {
   632  		return nil, errIncompleteRequest
   633  	}
   634  	_, overallError := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (interface{}, error) {
   635  		om, err := orderToModel(&corepb.Order{
   636  			Id:    req.Id,
   637  			Error: req.Error,
   638  		})
   639  		if err != nil {
   640  			return nil, err
   641  		}
   642  
   643  		result, err := tx.ExecContext(ctx, `
   644  		UPDATE orders
   645  		SET error = ?
   646  		WHERE id = ?`,
   647  			om.Error,
   648  			om.ID)
   649  		if err != nil {
   650  			return nil, berrors.InternalServerError("error updating order error field")
   651  		}
   652  
   653  		n, err := result.RowsAffected()
   654  		if err != nil || n == 0 {
   655  			return nil, berrors.InternalServerError("no order updated with new error field")
   656  		}
   657  
   658  		return nil, nil
   659  	})
   660  	if overallError != nil {
   661  		return nil, overallError
   662  	}
   663  	return &emptypb.Empty{}, nil
   664  }
   665  
   666  // FinalizeOrder finalizes a provided *corepb.Order by persisting the
   667  // CertificateSerial and a valid status to the database. No fields other than
   668  // CertificateSerial and the order ID on the provided order are processed (e.g.
   669  // this is not a generic update RPC).
   670  func (ssa *SQLStorageAuthority) FinalizeOrder(ctx context.Context, req *sapb.FinalizeOrderRequest) (*emptypb.Empty, error) {
   671  	if req.Id == 0 || req.CertificateSerial == "" {
   672  		return nil, errIncompleteRequest
   673  	}
   674  	_, overallError := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (interface{}, error) {
   675  		result, err := tx.ExecContext(ctx, `
   676  		UPDATE orders
   677  		SET certificateSerial = ?
   678  		WHERE id = ? AND
   679  		beganProcessing = true`,
   680  			req.CertificateSerial,
   681  			req.Id)
   682  		if err != nil {
   683  			return nil, berrors.InternalServerError("error updating order for finalization")
   684  		}
   685  
   686  		n, err := result.RowsAffected()
   687  		if err != nil || n == 0 {
   688  			return nil, berrors.InternalServerError("no order updated for finalization")
   689  		}
   690  
   691  		// Delete the orderFQDNSet row for the order now that it has been finalized.
   692  		// We use this table for order reuse and should not reuse a finalized order.
   693  		err = deleteOrderFQDNSet(ctx, tx, req.Id)
   694  		if err != nil {
   695  			return nil, err
   696  		}
   697  
   698  		return nil, nil
   699  	})
   700  	if overallError != nil {
   701  		return nil, overallError
   702  	}
   703  	return &emptypb.Empty{}, nil
   704  }
   705  
   706  // FinalizeAuthorization2 moves a pending authorization to either the valid or invalid status. If
   707  // the authorization is being moved to invalid the validationError field must be set. If the
   708  // authorization is being moved to valid the validationRecord and expires fields must be set.
   709  func (ssa *SQLStorageAuthority) FinalizeAuthorization2(ctx context.Context, req *sapb.FinalizeAuthorizationRequest) (*emptypb.Empty, error) {
   710  	if req.Status == "" || req.Attempted == "" || req.ExpiresNS == 0 || req.Id == 0 {
   711  		return nil, errIncompleteRequest
   712  	}
   713  
   714  	if req.Status != string(core.StatusValid) && req.Status != string(core.StatusInvalid) {
   715  		return nil, berrors.InternalServerError("authorization must have status valid or invalid")
   716  	}
   717  	query := `UPDATE authz2 SET
   718  		status = :status,
   719  		attempted = :attempted,
   720  		attemptedAt = :attemptedAt,
   721  		validationRecord = :validationRecord,
   722  		validationError = :validationError,
   723  		expires = :expires
   724  		WHERE id = :id AND status = :pending`
   725  	var validationRecords []core.ValidationRecord
   726  	for _, recordPB := range req.ValidationRecords {
   727  		record, err := bgrpc.PBToValidationRecord(recordPB)
   728  		if err != nil {
   729  			return nil, err
   730  		}
   731  		if req.Attempted == string(core.ChallengeTypeHTTP01) {
   732  			// Remove these fields because they can be rehydrated later
   733  			// on from the URL field.
   734  			record.Hostname = ""
   735  			record.Port = ""
   736  		}
   737  		validationRecords = append(validationRecords, record)
   738  	}
   739  	vrJSON, err := json.Marshal(validationRecords)
   740  	if err != nil {
   741  		return nil, err
   742  	}
   743  	var veJSON []byte
   744  	if req.ValidationError != nil {
   745  		validationError, err := bgrpc.PBToProblemDetails(req.ValidationError)
   746  		if err != nil {
   747  			return nil, err
   748  		}
   749  		j, err := json.Marshal(validationError)
   750  		if err != nil {
   751  			return nil, err
   752  		}
   753  		veJSON = j
   754  	}
   755  	// Check to see if the AttemptedAt time is non zero and convert to
   756  	// *time.Time if so. If it is zero, leave nil and don't convert. Keep
   757  	// the the database attemptedAt field Null instead of
   758  	// 1970-01-01 00:00:00.
   759  	var attemptedTime *time.Time
   760  	if req.AttemptedAtNS != 0 {
   761  		val := time.Unix(0, req.AttemptedAtNS).UTC()
   762  		attemptedTime = &val
   763  	}
   764  	params := map[string]interface{}{
   765  		"status":           statusToUint[core.AcmeStatus(req.Status)],
   766  		"attempted":        challTypeToUint[req.Attempted],
   767  		"attemptedAt":      attemptedTime,
   768  		"validationRecord": vrJSON,
   769  		"id":               req.Id,
   770  		"pending":          statusUint(core.StatusPending),
   771  		"expires":          time.Unix(0, req.ExpiresNS).UTC(),
   772  		// if req.ValidationError is nil veJSON should also be nil
   773  		// which should result in a NULL field
   774  		"validationError": veJSON,
   775  	}
   776  
   777  	res, err := ssa.dbMap.ExecContext(ctx, query, params)
   778  	if err != nil {
   779  		return nil, err
   780  	}
   781  	rows, err := res.RowsAffected()
   782  	if err != nil {
   783  		return nil, err
   784  	}
   785  	if rows == 0 {
   786  		return nil, berrors.NotFoundError("authorization with id %d not found", req.Id)
   787  	} else if rows > 1 {
   788  		return nil, berrors.InternalServerError("multiple rows updated for authorization id %d", req.Id)
   789  	}
   790  	return &emptypb.Empty{}, nil
   791  }
   792  
   793  // addRevokedCertificate is a helper used by both RevokeCertificate and
   794  // UpdateRevokedCertificate. It inserts a new row into the revokedCertificates
   795  // table based on the contents of the input request. The second argument must be
   796  // a transaction object so that it is safe to conduct multiple queries with a
   797  // consistent view of the database. It must only be called when the request
   798  // specifies a non-zero ShardIdx.
   799  func addRevokedCertificate(ctx context.Context, tx db.Executor, req *sapb.RevokeCertificateRequest, revokedDate time.Time) error {
   800  	if req.ShardIdx == 0 {
   801  		return errors.New("cannot add revoked certificate with shard index 0")
   802  	}
   803  
   804  	var serial struct {
   805  		Expires time.Time
   806  	}
   807  	err := tx.SelectOne(
   808  		ctx, &serial, `SELECT expires FROM serials WHERE serial = ?`, req.Serial)
   809  	if err != nil {
   810  		return fmt.Errorf("retrieving revoked certificate expiration: %w", err)
   811  	}
   812  
   813  	err = tx.Insert(ctx, &revokedCertModel{
   814  		IssuerID:      req.IssuerID,
   815  		Serial:        req.Serial,
   816  		ShardIdx:      req.ShardIdx,
   817  		RevokedDate:   revokedDate,
   818  		RevokedReason: revocation.Reason(req.Reason),
   819  		// Round the notAfter up to the next hour, to reduce index size while still
   820  		// ensuring we correctly serve revocation info past the actual expiration.
   821  		NotAfterHour: serial.Expires.Add(time.Hour).Truncate(time.Hour),
   822  	})
   823  	if err != nil {
   824  		return fmt.Errorf("inserting revoked certificate row: %w", err)
   825  	}
   826  
   827  	return nil
   828  }
   829  
   830  // RevokeCertificate stores revocation information about a certificate. It will only store this
   831  // information if the certificate is not already marked as revoked.
   832  func (ssa *SQLStorageAuthority) RevokeCertificate(ctx context.Context, req *sapb.RevokeCertificateRequest) (*emptypb.Empty, error) {
   833  	if req.Serial == "" || req.DateNS == 0 || req.IssuerID == 0 {
   834  		return nil, errIncompleteRequest
   835  	}
   836  
   837  	_, overallError := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (interface{}, error) {
   838  		revokedDate := time.Unix(0, req.DateNS)
   839  
   840  		res, err := tx.ExecContext(ctx,
   841  			`UPDATE certificateStatus SET
   842  				status = ?,
   843  				revokedReason = ?,
   844  				revokedDate = ?,
   845  				ocspLastUpdated = ?
   846  			WHERE serial = ? AND status != ?`,
   847  			string(core.OCSPStatusRevoked),
   848  			revocation.Reason(req.Reason),
   849  			revokedDate,
   850  			revokedDate,
   851  			req.Serial,
   852  			string(core.OCSPStatusRevoked),
   853  		)
   854  		if err != nil {
   855  			return nil, err
   856  		}
   857  		rows, err := res.RowsAffected()
   858  		if err != nil {
   859  			return nil, err
   860  		}
   861  		if rows == 0 {
   862  			return nil, berrors.AlreadyRevokedError("no certificate with serial %s and status other than %s", req.Serial, string(core.OCSPStatusRevoked))
   863  		}
   864  
   865  		if req.ShardIdx != 0 {
   866  			err = addRevokedCertificate(ctx, tx, req, revokedDate)
   867  			if err != nil {
   868  				return nil, err
   869  			}
   870  		}
   871  
   872  		return nil, nil
   873  	})
   874  	if overallError != nil {
   875  		return nil, overallError
   876  	}
   877  
   878  	return &emptypb.Empty{}, nil
   879  }
   880  
   881  // UpdateRevokedCertificate stores new revocation information about an
   882  // already-revoked certificate. It will only store this information if the
   883  // cert is already revoked, if the new revocation reason is `KeyCompromise`,
   884  // and if the revokedDate is identical to the current revokedDate.
   885  func (ssa *SQLStorageAuthority) UpdateRevokedCertificate(ctx context.Context, req *sapb.RevokeCertificateRequest) (*emptypb.Empty, error) {
   886  	if req.Serial == "" || req.DateNS == 0 || req.BackdateNS == 0 || req.IssuerID == 0 {
   887  		return nil, errIncompleteRequest
   888  	}
   889  	if req.Reason != ocsp.KeyCompromise {
   890  		return nil, fmt.Errorf("cannot update revocation for any reason other than keyCompromise (1); got: %d", req.Reason)
   891  	}
   892  
   893  	_, overallError := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (interface{}, error) {
   894  		thisUpdate := time.Unix(0, req.DateNS)
   895  		revokedDate := time.Unix(0, req.BackdateNS)
   896  
   897  		res, err := tx.ExecContext(ctx,
   898  			`UPDATE certificateStatus SET
   899  					revokedReason = ?,
   900  					ocspLastUpdated = ?
   901  				WHERE serial = ? AND status = ? AND revokedReason != ? AND revokedDate = ?`,
   902  			revocation.Reason(ocsp.KeyCompromise),
   903  			thisUpdate,
   904  			req.Serial,
   905  			string(core.OCSPStatusRevoked),
   906  			revocation.Reason(ocsp.KeyCompromise),
   907  			revokedDate,
   908  		)
   909  		if err != nil {
   910  			return nil, err
   911  		}
   912  		rows, err := res.RowsAffected()
   913  		if err != nil {
   914  			return nil, err
   915  		}
   916  		if rows == 0 {
   917  			// InternalServerError because we expected this certificate status to exist,
   918  			// to already be revoked for a different reason, and to have a matching date.
   919  			return nil, berrors.InternalServerError("no certificate with serial %s and revoked reason other than keyCompromise", req.Serial)
   920  		}
   921  
   922  		// Only update the revokedCertificates table if the revocation request
   923  		// specifies the CRL shard that this certificate belongs in. Our shards are
   924  		// one-indexed, so a ShardIdx of zero means no value was set.
   925  		if req.ShardIdx != 0 {
   926  			var rcm revokedCertModel
   927  			// Note: this query MUST be updated to enforce the same preconditions as
   928  			// the "UPDATE certificateStatus SET revokedReason..." above if this
   929  			// query ever becomes the first or only query in this transaction. We are
   930  			// currently relying on the query above to exit early if the certificate
   931  			// does not have an appropriate status.
   932  			err = tx.SelectOne(
   933  				ctx, &rcm, `SELECT * FROM revokedCertificates WHERE serial = ?`, req.Serial)
   934  			if db.IsNoRows(err) {
   935  				// TODO: Remove this fallback codepath once we know that all unexpired
   936  				// certs marked as revoked in the certificateStatus table have
   937  				// corresponding rows in the revokedCertificates table. That should be
   938  				// 90+ days after the RA starts sending ShardIdx in its
   939  				// RevokeCertificateRequest messages.
   940  				err = addRevokedCertificate(ctx, tx, req, revokedDate)
   941  				if err != nil {
   942  					return nil, err
   943  				}
   944  				return nil, nil
   945  			} else if err != nil {
   946  				return nil, fmt.Errorf("retrieving revoked certificate row: %w", err)
   947  			}
   948  
   949  			rcm.RevokedReason = revocation.Reason(ocsp.KeyCompromise)
   950  			_, err = tx.Update(ctx, &rcm)
   951  			if err != nil {
   952  				return nil, fmt.Errorf("updating revoked certificate row: %w", err)
   953  			}
   954  		}
   955  
   956  		return nil, nil
   957  	})
   958  	if overallError != nil {
   959  		return nil, overallError
   960  	}
   961  
   962  	return &emptypb.Empty{}, nil
   963  }
   964  
   965  // AddBlockedKey adds a key hash to the blockedKeys table
   966  func (ssa *SQLStorageAuthority) AddBlockedKey(ctx context.Context, req *sapb.AddBlockedKeyRequest) (*emptypb.Empty, error) {
   967  	if core.IsAnyNilOrZero(req.KeyHash, req.AddedNS, req.Source) {
   968  		return nil, errIncompleteRequest
   969  	}
   970  	sourceInt, ok := stringToSourceInt[req.Source]
   971  	if !ok {
   972  		return nil, errors.New("unknown source")
   973  	}
   974  	cols, qs := blockedKeysColumns, "?, ?, ?, ?"
   975  	vals := []interface{}{
   976  		req.KeyHash,
   977  		time.Unix(0, req.AddedNS),
   978  		sourceInt,
   979  		req.Comment,
   980  	}
   981  	if req.RevokedBy != 0 {
   982  		cols += ", revokedBy"
   983  		qs += ", ?"
   984  		vals = append(vals, req.RevokedBy)
   985  	}
   986  	_, err := ssa.dbMap.ExecContext(ctx,
   987  		fmt.Sprintf("INSERT INTO blockedKeys (%s) VALUES (%s)", cols, qs),
   988  		vals...,
   989  	)
   990  	if err != nil {
   991  		if db.IsDuplicate(err) {
   992  			// Ignore duplicate inserts so multiple certs with the same key can
   993  			// be revoked.
   994  			return &emptypb.Empty{}, nil
   995  		}
   996  		return nil, err
   997  	}
   998  	return &emptypb.Empty{}, nil
   999  }
  1000  
  1001  // Health implements the grpc.checker interface.
  1002  func (ssa *SQLStorageAuthority) Health(ctx context.Context) error {
  1003  	err := ssa.dbMap.SelectOne(ctx, new(int), "SELECT 1")
  1004  	if err != nil {
  1005  		return err
  1006  	}
  1007  
  1008  	err = ssa.SQLStorageAuthorityRO.Health(ctx)
  1009  	if err != nil {
  1010  		return err
  1011  	}
  1012  	return nil
  1013  }
  1014  
  1015  // LeaseCRLShard marks a single crlShards row as leased until the given time.
  1016  // If the request names a specific shard, this function will return an error
  1017  // if that shard is already leased. Otherwise, this function will return the
  1018  // index of the oldest shard for the given issuer.
  1019  func (ssa *SQLStorageAuthority) LeaseCRLShard(ctx context.Context, req *sapb.LeaseCRLShardRequest) (*sapb.LeaseCRLShardResponse, error) {
  1020  	if core.IsAnyNilOrZero(req.Until, req.IssuerNameID) {
  1021  		return nil, errIncompleteRequest
  1022  	}
  1023  	if req.Until.AsTime().Before(ssa.clk.Now()) {
  1024  		return nil, fmt.Errorf("lease timestamp must be in the future, got %q", req.Until.AsTime())
  1025  	}
  1026  
  1027  	if req.MinShardIdx == req.MaxShardIdx {
  1028  		return ssa.leaseSpecificCRLShard(ctx, req)
  1029  	}
  1030  
  1031  	return ssa.leaseOldestCRLShard(ctx, req)
  1032  }
  1033  
  1034  // leaseOldestCRLShard finds the oldest unleased crl shard for the given issuer
  1035  // and then leases it. Shards within the requested range which have never been
  1036  // leased or are previously-unknown indices are considered older than any other
  1037  // shard. It returns an error if all shards for the issuer are already leased.
  1038  func (ssa *SQLStorageAuthority) leaseOldestCRLShard(ctx context.Context, req *sapb.LeaseCRLShardRequest) (*sapb.LeaseCRLShardResponse, error) {
  1039  	shardIdx, err := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (interface{}, error) {
  1040  		var shards []*crlShardModel
  1041  		_, err := tx.Select(
  1042  			ctx,
  1043  			&shards,
  1044  			`SELECT id, issuerID, idx, thisUpdate, nextUpdate, leasedUntil
  1045  				FROM crlShards
  1046  				WHERE issuerID = ?
  1047  				AND idx BETWEEN ? AND ?`,
  1048  			req.IssuerNameID, req.MinShardIdx, req.MaxShardIdx,
  1049  		)
  1050  		if err != nil {
  1051  			return -1, fmt.Errorf("selecting candidate shards: %w", err)
  1052  		}
  1053  
  1054  		// Determine which shard index we want to lease.
  1055  		var shardIdx int
  1056  		var needToInsert bool
  1057  		if len(shards) < (int(req.MaxShardIdx + 1 - req.MinShardIdx)) {
  1058  			// Some expected shards are missing (i.e. never-before-produced), so we
  1059  			// pick one at random.
  1060  			missing := make(map[int]struct{}, req.MaxShardIdx+1-req.MinShardIdx)
  1061  			for i := req.MinShardIdx; i <= req.MaxShardIdx; i++ {
  1062  				missing[int(i)] = struct{}{}
  1063  			}
  1064  			for _, shard := range shards {
  1065  				delete(missing, shard.Idx)
  1066  			}
  1067  			for idx := range missing {
  1068  				// Go map iteration is guaranteed to be in randomized key order.
  1069  				shardIdx = idx
  1070  				break
  1071  			}
  1072  			needToInsert = true
  1073  		} else {
  1074  			// We got all the shards we expect, so we pick the oldest unleased shard.
  1075  			var oldest *crlShardModel
  1076  			for _, shard := range shards {
  1077  				if shard.LeasedUntil.After(ssa.clk.Now()) {
  1078  					continue
  1079  				}
  1080  				if oldest == nil ||
  1081  					(oldest.ThisUpdate != nil && shard.ThisUpdate == nil) ||
  1082  					(oldest.ThisUpdate != nil && shard.ThisUpdate.Before(*oldest.ThisUpdate)) {
  1083  					oldest = shard
  1084  				}
  1085  			}
  1086  			if oldest == nil {
  1087  				return -1, fmt.Errorf("issuer %d has no unleased shards in range %d-%d", req.IssuerNameID, req.MinShardIdx, req.MaxShardIdx)
  1088  			}
  1089  			shardIdx = oldest.Idx
  1090  			needToInsert = false
  1091  		}
  1092  
  1093  		if needToInsert {
  1094  			_, err = tx.ExecContext(ctx,
  1095  				`INSERT INTO crlShards (issuerID, idx, leasedUntil)
  1096  					VALUES (?, ?, ?)`,
  1097  				req.IssuerNameID,
  1098  				shardIdx,
  1099  				req.Until.AsTime(),
  1100  			)
  1101  			if err != nil {
  1102  				return -1, fmt.Errorf("inserting selected shard: %w", err)
  1103  			}
  1104  		} else {
  1105  			_, err = tx.ExecContext(ctx,
  1106  				`UPDATE crlShards
  1107  					SET leasedUntil = ?
  1108  					WHERE issuerID = ?
  1109  					AND idx = ?
  1110  					LIMIT 1`,
  1111  				req.Until.AsTime(),
  1112  				req.IssuerNameID,
  1113  				shardIdx,
  1114  			)
  1115  			if err != nil {
  1116  				return -1, fmt.Errorf("updating selected shard: %w", err)
  1117  			}
  1118  		}
  1119  
  1120  		return shardIdx, err
  1121  	})
  1122  	if err != nil {
  1123  		return nil, fmt.Errorf("leasing oldest shard: %w", err)
  1124  	}
  1125  
  1126  	return &sapb.LeaseCRLShardResponse{
  1127  		IssuerNameID: req.IssuerNameID,
  1128  		ShardIdx:     int64(shardIdx.(int)),
  1129  	}, nil
  1130  }
  1131  
  1132  // leaseSpecificCRLShard attempts to lease the crl shard for the given issuer
  1133  // and shard index. It returns an error if the specified shard is already
  1134  // leased.
  1135  func (ssa *SQLStorageAuthority) leaseSpecificCRLShard(ctx context.Context, req *sapb.LeaseCRLShardRequest) (*sapb.LeaseCRLShardResponse, error) {
  1136  	if req.MinShardIdx != req.MaxShardIdx {
  1137  		return nil, fmt.Errorf("request must identify a single shard index: %d != %d", req.MinShardIdx, req.MaxShardIdx)
  1138  	}
  1139  
  1140  	_, err := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (interface{}, error) {
  1141  		needToInsert := false
  1142  		var shardModel crlShardModel
  1143  		err := tx.SelectOne(ctx,
  1144  			&shardModel,
  1145  			`SELECT leasedUntil
  1146  			  FROM crlShards
  1147  				WHERE issuerID = ?
  1148  				AND idx = ?
  1149  				LIMIT 1`,
  1150  			req.IssuerNameID,
  1151  			req.MinShardIdx,
  1152  		)
  1153  		if db.IsNoRows(err) {
  1154  			needToInsert = true
  1155  		} else if err != nil {
  1156  			return nil, fmt.Errorf("selecting requested shard: %w", err)
  1157  		} else if shardModel.LeasedUntil.After(ssa.clk.Now()) {
  1158  			return nil, fmt.Errorf("shard %d for issuer %d already leased", req.MinShardIdx, req.IssuerNameID)
  1159  		}
  1160  
  1161  		if needToInsert {
  1162  			_, err = tx.ExecContext(ctx,
  1163  				`INSERT INTO crlShards (issuerID, idx, leasedUntil)
  1164  					VALUES (?, ?, ?)`,
  1165  				req.IssuerNameID,
  1166  				req.MinShardIdx,
  1167  				req.Until.AsTime(),
  1168  			)
  1169  			if err != nil {
  1170  				return nil, fmt.Errorf("inserting selected shard: %w", err)
  1171  			}
  1172  		} else {
  1173  			_, err = tx.ExecContext(ctx,
  1174  				`UPDATE crlShards
  1175  					SET leasedUntil = ?
  1176  					WHERE issuerID = ?
  1177  					AND idx = ?
  1178  					LIMIT 1`,
  1179  				req.Until.AsTime(),
  1180  				req.IssuerNameID,
  1181  				req.MinShardIdx,
  1182  			)
  1183  			if err != nil {
  1184  				return nil, fmt.Errorf("updating selected shard: %w", err)
  1185  			}
  1186  		}
  1187  
  1188  		return nil, nil
  1189  	})
  1190  	if err != nil {
  1191  		return nil, fmt.Errorf("leasing specific shard: %w", err)
  1192  	}
  1193  
  1194  	return &sapb.LeaseCRLShardResponse{
  1195  		IssuerNameID: req.IssuerNameID,
  1196  		ShardIdx:     req.MinShardIdx,
  1197  	}, nil
  1198  }
  1199  
  1200  // UpdateCRLShard updates the thisUpdate and nextUpdate timestamps of a CRL
  1201  // shard. It rejects the update if it would cause the thisUpdate timestamp to
  1202  // move backwards. It does *not* reject the update if the shard is no longer
  1203  // leased: although this would be unexpected (because the lease timestamp should
  1204  // be the same as the crl-updater's context expiration), it's not inherently a
  1205  // sign of an update that should be skipped. It does reject the update if the
  1206  // identified CRL shard does not exist in the database (it should exist, as
  1207  // rows are created if necessary when leased). It also sets the leasedUntil time
  1208  // to be equal to thisUpdate, to indicate that the shard is no longer leased.
  1209  func (ssa *SQLStorageAuthority) UpdateCRLShard(ctx context.Context, req *sapb.UpdateCRLShardRequest) (*emptypb.Empty, error) {
  1210  	if core.IsAnyNilOrZero(req.IssuerNameID, req.ThisUpdate) {
  1211  		return nil, errIncompleteRequest
  1212  	}
  1213  
  1214  	// Only set the nextUpdate if it's actually present in the request message.
  1215  	var nextUpdate *time.Time
  1216  	if req.NextUpdate != nil {
  1217  		nut := req.NextUpdate.AsTime()
  1218  		nextUpdate = &nut
  1219  	}
  1220  
  1221  	_, err := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (interface{}, error) {
  1222  		res, err := tx.ExecContext(ctx,
  1223  			`UPDATE crlShards
  1224  				SET thisUpdate = ?, nextUpdate = ?, leasedUntil = ?
  1225  				WHERE issuerID = ?
  1226  				AND idx = ?
  1227  				AND (thisUpdate is NULL OR thisUpdate < ?)
  1228  				LIMIT 1`,
  1229  			req.ThisUpdate.AsTime(),
  1230  			nextUpdate,
  1231  			req.ThisUpdate.AsTime(),
  1232  			req.IssuerNameID,
  1233  			req.ShardIdx,
  1234  			req.ThisUpdate.AsTime(),
  1235  		)
  1236  		if err != nil {
  1237  			return nil, err
  1238  		}
  1239  
  1240  		rowsAffected, err := res.RowsAffected()
  1241  		if err != nil {
  1242  			return nil, err
  1243  		}
  1244  		if rowsAffected == 0 {
  1245  			return nil, fmt.Errorf("unable to update shard %d for issuer %d", req.ShardIdx, req.IssuerNameID)
  1246  		}
  1247  		if rowsAffected != 1 {
  1248  			return nil, errors.New("update affected unexpected number of rows")
  1249  		}
  1250  		return nil, nil
  1251  	})
  1252  	if err != nil {
  1253  		return nil, err
  1254  	}
  1255  
  1256  	return &emptypb.Empty{}, nil
  1257  }
  1258  

View as plain text