...

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

Documentation: github.com/letsencrypt/boulder/sa

     1  package sa
     2  
     3  import (
     4  	"context"
     5  	"crypto/sha256"
     6  	"crypto/x509"
     7  	"encoding/base64"
     8  	"encoding/json"
     9  	"errors"
    10  	"fmt"
    11  	"math"
    12  	"net"
    13  	"net/url"
    14  	"slices"
    15  	"strconv"
    16  	"time"
    17  
    18  	"google.golang.org/protobuf/types/known/timestamppb"
    19  	"gopkg.in/go-jose/go-jose.v2"
    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/grpc"
    26  	"github.com/letsencrypt/boulder/identifier"
    27  	"github.com/letsencrypt/boulder/probs"
    28  	"github.com/letsencrypt/boulder/revocation"
    29  	sapb "github.com/letsencrypt/boulder/sa/proto"
    30  )
    31  
    32  // errBadJSON is an error type returned when a json.Unmarshal performed by the
    33  // SA fails. It includes both the Unmarshal error and the original JSON data in
    34  // its error message to make it easier to track down the bad JSON data.
    35  type errBadJSON struct {
    36  	msg  string
    37  	json []byte
    38  	err  error
    39  }
    40  
    41  // Error returns an error message that includes the json.Unmarshal error as well
    42  // as the bad JSON data.
    43  func (e errBadJSON) Error() string {
    44  	return fmt.Sprintf(
    45  		"%s: error unmarshaling JSON %q: %s",
    46  		e.msg,
    47  		string(e.json),
    48  		e.err)
    49  }
    50  
    51  // badJSONError is a convenience function for constructing a errBadJSON instance
    52  // with the provided args.
    53  func badJSONError(msg string, jsonData []byte, err error) error {
    54  	return errBadJSON{
    55  		msg:  msg,
    56  		json: jsonData,
    57  		err:  err,
    58  	}
    59  }
    60  
    61  const regFields = "id, jwk, jwk_sha256, contact, agreement, initialIP, createdAt, LockCol, status"
    62  
    63  // ClearEmail removes the provided email address from one specified registration. If
    64  // there are multiple email addresses present, it does not modify other ones. If the email
    65  // address is not present, it does not modify the registration and will return a nil error.
    66  func ClearEmail(ctx context.Context, dbMap db.DatabaseMap, regID int64, email string) error {
    67  	_, overallError := db.WithTransaction(ctx, dbMap, func(tx db.Executor) (interface{}, error) {
    68  		curr, err := selectRegistration(ctx, tx, "id", regID)
    69  		if err != nil {
    70  			return nil, err
    71  		}
    72  
    73  		currPb, err := registrationModelToPb(curr)
    74  		if err != nil {
    75  			return nil, err
    76  		}
    77  
    78  		// newContacts will be a copy of all emails in currPb.Contact _except_ the one to be removed
    79  		var newContacts []string
    80  		for _, contact := range currPb.Contact {
    81  			if contact != "mailto:"+email {
    82  				newContacts = append(newContacts, contact)
    83  			}
    84  		}
    85  
    86  		if slices.Equal(currPb.Contact, newContacts) {
    87  			return nil, nil
    88  		}
    89  
    90  		currPb.Contact = newContacts
    91  		newModel, err := registrationPbToModel(currPb)
    92  		if err != nil {
    93  			return nil, err
    94  		}
    95  
    96  		return tx.Update(ctx, newModel)
    97  	})
    98  	if overallError != nil {
    99  		return overallError
   100  	}
   101  
   102  	return nil
   103  }
   104  
   105  // selectRegistration selects all fields of one registration model
   106  func selectRegistration(ctx context.Context, s db.OneSelector, whereCol string, args ...interface{}) (*regModel, error) {
   107  	if whereCol != "id" && whereCol != "jwk_sha256" {
   108  		return nil, fmt.Errorf("column name %q invalid for registrations table WHERE clause", whereCol)
   109  	}
   110  
   111  	var model regModel
   112  	err := s.SelectOne(
   113  		ctx,
   114  		&model,
   115  		"SELECT "+regFields+" FROM registrations WHERE "+whereCol+" = ? LIMIT 1",
   116  		args...,
   117  	)
   118  	return &model, err
   119  }
   120  
   121  const certFields = "registrationID, serial, digest, der, issued, expires"
   122  
   123  // SelectCertificate selects all fields of one certificate object identified by
   124  // a serial. If more than one row contains the same serial only the first is
   125  // returned.
   126  func SelectCertificate(ctx context.Context, s db.OneSelector, serial string) (core.Certificate, error) {
   127  	var model core.Certificate
   128  	err := s.SelectOne(
   129  		ctx,
   130  		&model,
   131  		"SELECT "+certFields+" FROM certificates WHERE serial = ? LIMIT 1",
   132  		serial,
   133  	)
   134  	return model, err
   135  }
   136  
   137  const precertFields = "registrationID, serial, der, issued, expires"
   138  
   139  // SelectPrecertificate selects all fields of one precertificate object
   140  // identified by serial.
   141  func SelectPrecertificate(ctx context.Context, s db.OneSelector, serial string) (core.Certificate, error) {
   142  	var model precertificateModel
   143  	err := s.SelectOne(
   144  		ctx,
   145  		&model,
   146  		"SELECT "+precertFields+" FROM precertificates WHERE serial = ? LIMIT 1",
   147  		serial)
   148  	return core.Certificate{
   149  		RegistrationID: model.RegistrationID,
   150  		Serial:         model.Serial,
   151  		DER:            model.DER,
   152  		Issued:         model.Issued,
   153  		Expires:        model.Expires,
   154  	}, err
   155  }
   156  
   157  type CertWithID struct {
   158  	ID int64
   159  	core.Certificate
   160  }
   161  
   162  // SelectCertificates selects all fields of multiple certificate objects
   163  func SelectCertificates(ctx context.Context, s db.Selector, q string, args map[string]interface{}) ([]CertWithID, error) {
   164  	var models []CertWithID
   165  	_, err := s.Select(
   166  		ctx,
   167  		&models,
   168  		"SELECT id, "+certFields+" FROM certificates "+q, args)
   169  	return models, err
   170  }
   171  
   172  // SelectPrecertificates selects all fields of multiple precertificate objects.
   173  func SelectPrecertificates(ctx context.Context, s db.Selector, q string, args map[string]interface{}) ([]CertWithID, error) {
   174  	var models []CertWithID
   175  	_, err := s.Select(
   176  		ctx,
   177  		&models,
   178  		"SELECT id, "+precertFields+" FROM precertificates "+q, args)
   179  	return models, err
   180  }
   181  
   182  type CertStatusMetadata struct {
   183  	ID                    int64             `db:"id"`
   184  	Serial                string            `db:"serial"`
   185  	Status                core.OCSPStatus   `db:"status"`
   186  	OCSPLastUpdated       time.Time         `db:"ocspLastUpdated"`
   187  	RevokedDate           time.Time         `db:"revokedDate"`
   188  	RevokedReason         revocation.Reason `db:"revokedReason"`
   189  	LastExpirationNagSent time.Time         `db:"lastExpirationNagSent"`
   190  	NotAfter              time.Time         `db:"notAfter"`
   191  	IsExpired             bool              `db:"isExpired"`
   192  	IssuerID              int64             `db:"issuerID"`
   193  }
   194  
   195  const certStatusFields = "id, serial, status, ocspLastUpdated, revokedDate, revokedReason, lastExpirationNagSent, notAfter, isExpired, issuerID"
   196  
   197  // SelectCertificateStatus selects all fields of one certificate status model
   198  // identified by serial
   199  func SelectCertificateStatus(ctx context.Context, s db.OneSelector, serial string) (core.CertificateStatus, error) {
   200  	var model core.CertificateStatus
   201  	err := s.SelectOne(
   202  		ctx,
   203  		&model,
   204  		"SELECT "+certStatusFields+" FROM certificateStatus WHERE serial = ? LIMIT 1",
   205  		serial,
   206  	)
   207  	return model, err
   208  }
   209  
   210  // RevocationStatusModel represents a small subset of the columns in the
   211  // certificateStatus table, used to determine the authoritative revocation
   212  // status of a certificate.
   213  type RevocationStatusModel struct {
   214  	Status        core.OCSPStatus   `db:"status"`
   215  	RevokedDate   time.Time         `db:"revokedDate"`
   216  	RevokedReason revocation.Reason `db:"revokedReason"`
   217  }
   218  
   219  // SelectRevocationStatus returns the authoritative revocation information for
   220  // the certificate with the given serial.
   221  func SelectRevocationStatus(ctx context.Context, s db.OneSelector, serial string) (*sapb.RevocationStatus, error) {
   222  	var model RevocationStatusModel
   223  	err := s.SelectOne(
   224  		ctx,
   225  		&model,
   226  		"SELECT status, revokedDate, revokedReason FROM certificateStatus WHERE serial = ? LIMIT 1",
   227  		serial,
   228  	)
   229  	if err != nil {
   230  		return nil, err
   231  	}
   232  
   233  	statusInt, ok := core.OCSPStatusToInt[model.Status]
   234  	if !ok {
   235  		return nil, fmt.Errorf("got unrecognized status %q", model.Status)
   236  	}
   237  
   238  	return &sapb.RevocationStatus{
   239  		Status:        int64(statusInt),
   240  		RevokedDate:   timestamppb.New(model.RevokedDate),
   241  		RevokedReason: int64(model.RevokedReason),
   242  	}, nil
   243  }
   244  
   245  var mediumBlobSize = int(math.Pow(2, 24))
   246  
   247  type issuedNameModel struct {
   248  	ID           int64     `db:"id"`
   249  	ReversedName string    `db:"reversedName"`
   250  	NotBefore    time.Time `db:"notBefore"`
   251  	Serial       string    `db:"serial"`
   252  }
   253  
   254  // regModel is the description of a core.Registration in the database before
   255  type regModel struct {
   256  	ID        int64  `db:"id"`
   257  	Key       []byte `db:"jwk"`
   258  	KeySHA256 string `db:"jwk_sha256"`
   259  	Contact   string `db:"contact"`
   260  	Agreement string `db:"agreement"`
   261  	// InitialIP is stored as sixteen binary bytes, regardless of whether it
   262  	// represents a v4 or v6 IP address.
   263  	InitialIP []byte    `db:"initialIp"`
   264  	CreatedAt time.Time `db:"createdAt"`
   265  	LockCol   int64
   266  	Status    string `db:"status"`
   267  }
   268  
   269  func registrationPbToModel(reg *corepb.Registration) (*regModel, error) {
   270  	// Even though we don't need to convert from JSON to an in-memory JSONWebKey
   271  	// for the sake of the `Key` field, we do need to do the conversion in order
   272  	// to compute the SHA256 key digest.
   273  	var jwk jose.JSONWebKey
   274  	err := jwk.UnmarshalJSON(reg.Key)
   275  	if err != nil {
   276  		return nil, err
   277  	}
   278  	sha, err := core.KeyDigestB64(jwk.Key)
   279  	if err != nil {
   280  		return nil, err
   281  	}
   282  
   283  	// We don't want to write literal JSON "null" strings into the database if the
   284  	// list of contact addresses is empty. Replace any possibly-`nil` slice with
   285  	// an empty JSON array. We don't need to check reg.ContactPresent, because
   286  	// we're going to write the whole object to the database anyway.
   287  	jsonContact := []byte("[]")
   288  	if len(reg.Contact) != 0 {
   289  		jsonContact, err = json.Marshal(reg.Contact)
   290  		if err != nil {
   291  			return nil, err
   292  		}
   293  	}
   294  
   295  	// For some reason we use different serialization formats for InitialIP
   296  	// in database models and in protobufs, despite the fact that both formats
   297  	// are just []byte.
   298  	var initialIP net.IP
   299  	err = initialIP.UnmarshalText(reg.InitialIP)
   300  	if err != nil {
   301  		return nil, err
   302  	}
   303  
   304  	// Converting the int64 zero-value to a unix timestamp does not produce
   305  	// the time.Time zero-value (the former is 1970; the latter is year 0),
   306  	// so we have to do this check.
   307  	var createdAt time.Time
   308  	if reg.CreatedAtNS != 0 {
   309  		createdAt = time.Unix(0, reg.CreatedAtNS)
   310  	}
   311  
   312  	return &regModel{
   313  		ID:        reg.Id,
   314  		Key:       reg.Key,
   315  		KeySHA256: sha,
   316  		Contact:   string(jsonContact),
   317  		Agreement: reg.Agreement,
   318  		InitialIP: []byte(initialIP.To16()),
   319  		CreatedAt: createdAt,
   320  		Status:    reg.Status,
   321  	}, nil
   322  }
   323  
   324  func registrationModelToPb(reg *regModel) (*corepb.Registration, error) {
   325  	if reg.ID == 0 || len(reg.Key) == 0 || len(reg.InitialIP) == 0 {
   326  		return nil, errors.New("incomplete Registration retrieved from DB")
   327  	}
   328  
   329  	contact := []string{}
   330  	contactsPresent := false
   331  	if len(reg.Contact) > 0 {
   332  		err := json.Unmarshal([]byte(reg.Contact), &contact)
   333  		if err != nil {
   334  			return nil, err
   335  		}
   336  		if len(contact) > 0 {
   337  			contactsPresent = true
   338  		}
   339  	}
   340  
   341  	// For some reason we use different serialization formats for InitialIP
   342  	// in database models and in protobufs, despite the fact that both formats
   343  	// are just []byte.
   344  	ipBytes, err := net.IP(reg.InitialIP).MarshalText()
   345  	if err != nil {
   346  		return nil, err
   347  	}
   348  
   349  	return &corepb.Registration{
   350  		Id:              reg.ID,
   351  		Key:             reg.Key,
   352  		Contact:         contact,
   353  		ContactsPresent: contactsPresent,
   354  		Agreement:       reg.Agreement,
   355  		InitialIP:       ipBytes,
   356  		CreatedAtNS:     reg.CreatedAt.UTC().UnixNano(),
   357  		CreatedAt:       timestamppb.New(reg.CreatedAt.UTC()),
   358  		Status:          reg.Status,
   359  	}, nil
   360  }
   361  
   362  type recordedSerialModel struct {
   363  	ID             int64
   364  	Serial         string
   365  	RegistrationID int64
   366  	Created        time.Time
   367  	Expires        time.Time
   368  }
   369  
   370  type precertificateModel struct {
   371  	ID             int64
   372  	Serial         string
   373  	RegistrationID int64
   374  	DER            []byte
   375  	Issued         time.Time
   376  	Expires        time.Time
   377  }
   378  
   379  type orderModel struct {
   380  	ID                int64
   381  	RegistrationID    int64
   382  	Expires           time.Time
   383  	Created           time.Time
   384  	Error             []byte
   385  	CertificateSerial string
   386  	BeganProcessing   bool
   387  }
   388  
   389  type requestedNameModel struct {
   390  	ID           int64
   391  	OrderID      int64
   392  	ReversedName string
   393  }
   394  
   395  type orderToAuthzModel struct {
   396  	OrderID int64
   397  	AuthzID int64
   398  }
   399  
   400  func orderToModel(order *corepb.Order) (*orderModel, error) {
   401  	om := &orderModel{
   402  		ID:                order.Id,
   403  		RegistrationID:    order.RegistrationID,
   404  		Expires:           time.Unix(0, order.ExpiresNS),
   405  		Created:           time.Unix(0, order.CreatedNS),
   406  		BeganProcessing:   order.BeganProcessing,
   407  		CertificateSerial: order.CertificateSerial,
   408  	}
   409  
   410  	if order.Error != nil {
   411  		errJSON, err := json.Marshal(order.Error)
   412  		if err != nil {
   413  			return nil, err
   414  		}
   415  		if len(errJSON) > mediumBlobSize {
   416  			return nil, fmt.Errorf("Error object is too large to store in the database")
   417  		}
   418  		om.Error = errJSON
   419  	}
   420  	return om, nil
   421  }
   422  
   423  func modelToOrder(om *orderModel) (*corepb.Order, error) {
   424  	order := &corepb.Order{
   425  		Id:                om.ID,
   426  		RegistrationID:    om.RegistrationID,
   427  		ExpiresNS:         om.Expires.UnixNano(),
   428  		Expires:           timestamppb.New(om.Expires),
   429  		CreatedNS:         om.Created.UnixNano(),
   430  		Created:           timestamppb.New(om.Created),
   431  		CertificateSerial: om.CertificateSerial,
   432  		BeganProcessing:   om.BeganProcessing,
   433  	}
   434  	if len(om.Error) > 0 {
   435  		var problem corepb.ProblemDetails
   436  		err := json.Unmarshal(om.Error, &problem)
   437  		if err != nil {
   438  			return &corepb.Order{}, badJSONError(
   439  				"failed to unmarshal order model's error",
   440  				om.Error,
   441  				err)
   442  		}
   443  		order.Error = &problem
   444  	}
   445  	return order, nil
   446  }
   447  
   448  var challTypeToUint = map[string]uint8{
   449  	"http-01":     0,
   450  	"dns-01":      1,
   451  	"tls-alpn-01": 2,
   452  }
   453  
   454  var uintToChallType = map[uint8]string{
   455  	0: "http-01",
   456  	1: "dns-01",
   457  	2: "tls-alpn-01",
   458  }
   459  
   460  var identifierTypeToUint = map[string]uint8{
   461  	"dns": 0,
   462  }
   463  
   464  var uintToIdentifierType = map[uint8]string{
   465  	0: "dns",
   466  }
   467  
   468  var statusToUint = map[core.AcmeStatus]uint8{
   469  	core.StatusPending:     0,
   470  	core.StatusValid:       1,
   471  	core.StatusInvalid:     2,
   472  	core.StatusDeactivated: 3,
   473  	core.StatusRevoked:     4,
   474  }
   475  
   476  var uintToStatus = map[uint8]core.AcmeStatus{
   477  	0: core.StatusPending,
   478  	1: core.StatusValid,
   479  	2: core.StatusInvalid,
   480  	3: core.StatusDeactivated,
   481  	4: core.StatusRevoked,
   482  }
   483  
   484  func statusUint(status core.AcmeStatus) uint8 {
   485  	return statusToUint[status]
   486  }
   487  
   488  // authzFields is used in a variety of places in sa.go, and modifications to
   489  // it must be carried through to every use in sa.go
   490  const authzFields = "id, identifierType, identifierValue, registrationID, status, expires, challenges, attempted, attemptedAt, token, validationError, validationRecord"
   491  
   492  type authzModel struct {
   493  	ID               int64      `db:"id"`
   494  	IdentifierType   uint8      `db:"identifierType"`
   495  	IdentifierValue  string     `db:"identifierValue"`
   496  	RegistrationID   int64      `db:"registrationID"`
   497  	Status           uint8      `db:"status"`
   498  	Expires          time.Time  `db:"expires"`
   499  	Challenges       uint8      `db:"challenges"`
   500  	Attempted        *uint8     `db:"attempted"`
   501  	AttemptedAt      *time.Time `db:"attemptedAt"`
   502  	Token            []byte     `db:"token"`
   503  	ValidationError  []byte     `db:"validationError"`
   504  	ValidationRecord []byte     `db:"validationRecord"`
   505  }
   506  
   507  // rehydrateHostPort mutates a validation record. If the URL in the validation
   508  // record cannot be parsed, an error will be returned. If the Hostname and Port
   509  // fields already exist in the validation record, they will be retained.
   510  // Otherwise, the Hostname and Port will be derived and set from the URL field
   511  // of the validation record.
   512  func rehydrateHostPort(vr *core.ValidationRecord) error {
   513  	if vr.URL == "" {
   514  		return fmt.Errorf("rehydrating validation record, URL field cannot be empty")
   515  	}
   516  
   517  	parsedUrl, err := url.Parse(vr.URL)
   518  	if err != nil {
   519  		return fmt.Errorf("parsing validation record URL %q: %w", vr.URL, err)
   520  	}
   521  
   522  	if vr.Hostname == "" {
   523  		hostname := parsedUrl.Hostname()
   524  		if hostname == "" {
   525  			return fmt.Errorf("hostname missing in URL %q", vr.URL)
   526  		}
   527  		vr.Hostname = hostname
   528  	}
   529  
   530  	if vr.Port == "" {
   531  		// CABF BRs section 1.6.1: Authorized Ports: One of the following ports: 80
   532  		// (http), 443 (https)
   533  		if parsedUrl.Port() == "" {
   534  			// If there is only a scheme, then we'll determine the appropriate port.
   535  			switch parsedUrl.Scheme {
   536  			case "https":
   537  				vr.Port = "443"
   538  			case "http":
   539  				vr.Port = "80"
   540  			default:
   541  				// This should never happen since the VA should have already
   542  				// checked the scheme.
   543  				return fmt.Errorf("unknown scheme %q in URL %q", parsedUrl.Scheme, vr.URL)
   544  			}
   545  		} else if parsedUrl.Port() == "80" || parsedUrl.Port() == "443" {
   546  			// If :80 or :443 were embedded in the URL field
   547  			// e.g. '"url":"https://example.com:443"'
   548  			vr.Port = parsedUrl.Port()
   549  		} else {
   550  			return fmt.Errorf("only ports 80/tcp and 443/tcp are allowed in URL %q", vr.URL)
   551  		}
   552  	}
   553  
   554  	return nil
   555  }
   556  
   557  // SelectAuthzsMatchingIssuance looks for a set of authzs that would have
   558  // authorized a given issuance that is known to have occurred. The returned
   559  // authzs will all belong to the given regID, will have potentially been valid
   560  // at the time of issuance, and will have the appropriate identifier type and
   561  // value. This may return multiple authzs for the same identifier type and value.
   562  //
   563  // This returns "potentially" valid authzs because a client may have set an
   564  // authzs status to deactivated after issuance, so we return both valid and
   565  // deactivated authzs. It also uses a small amount of leeway (1s) to account
   566  // for possible clock skew.
   567  //
   568  // This function doesn't do anything special for authzs with an expiration in
   569  // the past. If the stored authz has a valid status, it is returned with a
   570  // valid status regardless of whether it is also expired.
   571  func SelectAuthzsMatchingIssuance(
   572  	ctx context.Context,
   573  	s db.Selector,
   574  	regID int64,
   575  	issued time.Time,
   576  	dnsNames []string,
   577  ) ([]*corepb.Authorization, error) {
   578  	query := fmt.Sprintf(`SELECT %s FROM authz2 WHERE
   579  			registrationID = ? AND
   580  			status IN (?, ?) AND
   581  			expires >= ? AND
   582  			attemptedAt <= ? AND
   583  			identifierType = ? AND
   584  			identifierValue IN (%s)`,
   585  		authzFields,
   586  		db.QuestionMarks(len(dnsNames)))
   587  	var args []any
   588  	args = append(args,
   589  		regID,
   590  		statusToUint[core.StatusValid],
   591  		statusToUint[core.StatusDeactivated],
   592  		issued.Add(-1*time.Second), // leeway for clock skew
   593  		issued.Add(1*time.Second),  // leeway for clock skew
   594  		identifierTypeToUint[string(identifier.DNS)],
   595  	)
   596  	for _, name := range dnsNames {
   597  		args = append(args, name)
   598  	}
   599  
   600  	var authzModels []authzModel
   601  	_, err := s.Select(ctx, &authzModels, query, args...)
   602  	if err != nil {
   603  		return nil, err
   604  	}
   605  
   606  	var authzs []*corepb.Authorization
   607  	for _, model := range authzModels {
   608  		authz, err := modelToAuthzPB(model)
   609  		if err != nil {
   610  			return nil, err
   611  		}
   612  		authzs = append(authzs, authz)
   613  
   614  	}
   615  	return authzs, err
   616  }
   617  
   618  // hasMultipleNonPendingChallenges checks if a slice of challenges contains
   619  // more than one non-pending challenge
   620  func hasMultipleNonPendingChallenges(challenges []*corepb.Challenge) bool {
   621  	nonPending := false
   622  	for _, c := range challenges {
   623  		if c.Status == string(core.StatusValid) || c.Status == string(core.StatusInvalid) {
   624  			if !nonPending {
   625  				nonPending = true
   626  			} else {
   627  				return true
   628  			}
   629  		}
   630  	}
   631  	return false
   632  }
   633  
   634  // authzPBToModel converts a protobuf authorization representation to the
   635  // authzModel storage representation.
   636  func authzPBToModel(authz *corepb.Authorization) (*authzModel, error) {
   637  	am := &authzModel{
   638  		IdentifierValue: authz.Identifier,
   639  		RegistrationID:  authz.RegistrationID,
   640  		Status:          statusToUint[core.AcmeStatus(authz.Status)],
   641  		Expires:         time.Unix(0, authz.ExpiresNS).UTC(),
   642  	}
   643  	if authz.Id != "" {
   644  		// The v1 internal authorization objects use a string for the ID, the v2
   645  		// storage format uses a integer ID. In order to maintain compatibility we
   646  		// convert the integer ID to a string.
   647  		id, err := strconv.Atoi(authz.Id)
   648  		if err != nil {
   649  			return nil, err
   650  		}
   651  		am.ID = int64(id)
   652  	}
   653  	if hasMultipleNonPendingChallenges(authz.Challenges) {
   654  		return nil, errors.New("multiple challenges are non-pending")
   655  	}
   656  	// In the v2 authorization style we don't store individual challenges with their own
   657  	// token, validation errors/records, etc. Instead we store a single token/error/record
   658  	// set, a bitmap of available challenge types, and a row indicating which challenge type
   659  	// was 'attempted'.
   660  	//
   661  	// Since we don't currently have the singular token/error/record set abstracted out to
   662  	// the core authorization type yet we need to extract these from the challenges array.
   663  	// We assume that the token in each challenge is the same and that if any of the challenges
   664  	// has a non-pending status that it should be considered the 'attempted' challenge and
   665  	// we extract the error/record set from that particular challenge.
   666  	var tokenStr string
   667  	for _, chall := range authz.Challenges {
   668  		// Set the challenge type bit in the bitmap
   669  		am.Challenges |= 1 << challTypeToUint[chall.Type]
   670  		tokenStr = chall.Token
   671  		// If the challenge status is not core.StatusPending we assume it was the 'attempted'
   672  		// challenge and extract the relevant fields we need.
   673  		if chall.Status == string(core.StatusValid) || chall.Status == string(core.StatusInvalid) {
   674  			attemptedType := challTypeToUint[chall.Type]
   675  			am.Attempted = &attemptedType
   676  
   677  			// If validated Unix timestamp is zero then keep the core.Challenge Validated object nil.
   678  			var validated *time.Time
   679  			if chall.ValidatedNS != 0 {
   680  				val := time.Unix(0, chall.ValidatedNS).UTC()
   681  				validated = &val
   682  			}
   683  			am.AttemptedAt = validated
   684  
   685  			// Marshal corepb.ValidationRecords to core.ValidationRecords so that we
   686  			// can marshal them to JSON.
   687  			records := make([]core.ValidationRecord, len(chall.Validationrecords))
   688  			for i, recordPB := range chall.Validationrecords {
   689  				if chall.Type == string(core.ChallengeTypeHTTP01) {
   690  					// Remove these fields because they can be rehydrated later
   691  					// on from the URL field.
   692  					recordPB.Hostname = ""
   693  					recordPB.Port = ""
   694  				}
   695  				var err error
   696  				records[i], err = grpc.PBToValidationRecord(recordPB)
   697  				if err != nil {
   698  					return nil, err
   699  				}
   700  			}
   701  			var err error
   702  			am.ValidationRecord, err = json.Marshal(records)
   703  			if err != nil {
   704  				return nil, err
   705  			}
   706  			// If there is a error associated with the challenge marshal it to JSON
   707  			// so that we can store it in the database.
   708  			if chall.Error != nil {
   709  				prob, err := grpc.PBToProblemDetails(chall.Error)
   710  				if err != nil {
   711  					return nil, err
   712  				}
   713  				am.ValidationError, err = json.Marshal(prob)
   714  				if err != nil {
   715  					return nil, err
   716  				}
   717  			}
   718  		}
   719  		token, err := base64.RawURLEncoding.DecodeString(tokenStr)
   720  		if err != nil {
   721  			return nil, err
   722  		}
   723  		am.Token = token
   724  	}
   725  
   726  	return am, nil
   727  }
   728  
   729  // populateAttemptedFields takes a challenge and populates it with the validation fields status,
   730  // validation records, and error (the latter only if the validation failed) from an authzModel.
   731  func populateAttemptedFields(am authzModel, challenge *corepb.Challenge) error {
   732  	if len(am.ValidationError) != 0 {
   733  		// If the error is non-empty the challenge must be invalid.
   734  		challenge.Status = string(core.StatusInvalid)
   735  		var prob probs.ProblemDetails
   736  		err := json.Unmarshal(am.ValidationError, &prob)
   737  		if err != nil {
   738  			return badJSONError(
   739  				"failed to unmarshal authz2 model's validation error",
   740  				am.ValidationError,
   741  				err)
   742  		}
   743  		challenge.Error, err = grpc.ProblemDetailsToPB(&prob)
   744  		if err != nil {
   745  			return err
   746  		}
   747  	} else {
   748  		// If the error is empty the challenge must be valid.
   749  		challenge.Status = string(core.StatusValid)
   750  	}
   751  	var records []core.ValidationRecord
   752  	err := json.Unmarshal(am.ValidationRecord, &records)
   753  	if err != nil {
   754  		return badJSONError(
   755  			"failed to unmarshal authz2 model's validation record",
   756  			am.ValidationRecord,
   757  			err)
   758  	}
   759  	challenge.Validationrecords = make([]*corepb.ValidationRecord, len(records))
   760  	for i, r := range records {
   761  		// Fixes implicit memory aliasing in for loop so we can deference r
   762  		// later on for rehydrateHostPort.
   763  		r := r
   764  		if challenge.Type == string(core.ChallengeTypeHTTP01) {
   765  			err := rehydrateHostPort(&r)
   766  			if err != nil {
   767  				return err
   768  			}
   769  		}
   770  		challenge.Validationrecords[i], err = grpc.ValidationRecordToPB(r)
   771  		if err != nil {
   772  			return err
   773  		}
   774  	}
   775  	return nil
   776  }
   777  
   778  func modelToAuthzPB(am authzModel) (*corepb.Authorization, error) {
   779  	pb := &corepb.Authorization{
   780  		Id:             fmt.Sprintf("%d", am.ID),
   781  		Status:         string(uintToStatus[am.Status]),
   782  		Identifier:     am.IdentifierValue,
   783  		RegistrationID: am.RegistrationID,
   784  		ExpiresNS:      am.Expires.UTC().UnixNano(),
   785  		Expires:        timestamppb.New(am.Expires),
   786  	}
   787  	// Populate authorization challenge array. We do this by iterating through
   788  	// the challenge type bitmap and creating a challenge of each type if its
   789  	// bit is set. Each of these challenges has the token from the authorization
   790  	// model and has its status set to core.StatusPending by default. If the
   791  	// challenge type is equal to that in the 'attempted' row we set the status
   792  	// to core.StatusValid or core.StatusInvalid depending on if there is anything
   793  	// in ValidationError and populate the ValidationRecord and ValidationError
   794  	// fields.
   795  	for pos := uint8(0); pos < 8; pos++ {
   796  		if (am.Challenges>>pos)&1 == 1 {
   797  			challType := uintToChallType[pos]
   798  			challenge := &corepb.Challenge{
   799  				Type:   challType,
   800  				Status: string(core.StatusPending),
   801  				Token:  base64.RawURLEncoding.EncodeToString(am.Token),
   802  			}
   803  			// If the challenge type matches the attempted type it must be either
   804  			// valid or invalid and we need to populate extra fields.
   805  			// Also, once any challenge has been attempted, we consider the other
   806  			// challenges "gone" per https://tools.ietf.org/html/rfc8555#section-7.1.4
   807  			if am.Attempted != nil {
   808  				if uintToChallType[*am.Attempted] == challType {
   809  					err := populateAttemptedFields(am, challenge)
   810  					if err != nil {
   811  						return nil, err
   812  					}
   813  					// Get the attemptedAt time and assign to the challenge validated time.
   814  					var validatedInt int64 = 0
   815  					validatedTS := timestamppb.New(time.Time{})
   816  					if am.AttemptedAt != nil {
   817  						validatedInt = am.AttemptedAt.UTC().UnixNano()
   818  						validatedTS = timestamppb.New(*am.AttemptedAt)
   819  					}
   820  					challenge.ValidatedNS = validatedInt
   821  					challenge.Validated = validatedTS
   822  					pb.Challenges = append(pb.Challenges, challenge)
   823  				}
   824  			} else {
   825  				// When no challenge has been attempted yet, all challenges are still
   826  				// present.
   827  				pb.Challenges = append(pb.Challenges, challenge)
   828  			}
   829  		}
   830  	}
   831  	return pb, nil
   832  }
   833  
   834  type keyHashModel struct {
   835  	ID           int64
   836  	KeyHash      []byte
   837  	CertNotAfter time.Time
   838  	CertSerial   string
   839  }
   840  
   841  var stringToSourceInt = map[string]int{
   842  	"API":           1,
   843  	"admin-revoker": 2,
   844  }
   845  
   846  // incidentModel represents a row in the 'incidents' table.
   847  type incidentModel struct {
   848  	ID          int64     `db:"id"`
   849  	SerialTable string    `db:"serialTable"`
   850  	URL         string    `db:"url"`
   851  	RenewBy     time.Time `db:"renewBy"`
   852  	Enabled     bool      `db:"enabled"`
   853  }
   854  
   855  func incidentModelToPB(i incidentModel) sapb.Incident {
   856  	return sapb.Incident{
   857  		Id:          i.ID,
   858  		SerialTable: i.SerialTable,
   859  		Url:         i.URL,
   860  		RenewByNS:   i.RenewBy.UnixNano(),
   861  		RenewBy:     timestamppb.New(i.RenewBy),
   862  		Enabled:     i.Enabled,
   863  	}
   864  }
   865  
   866  // incidentSerialModel represents a row in an 'incident_*' table.
   867  type incidentSerialModel struct {
   868  	Serial         string     `db:"serial"`
   869  	RegistrationID *int64     `db:"registrationID"`
   870  	OrderID        *int64     `db:"orderID"`
   871  	LastNoticeSent *time.Time `db:"lastNoticeSent"`
   872  }
   873  
   874  // crlEntryModel has just the certificate status fields necessary to construct
   875  // an entry in a CRL.
   876  type crlEntryModel struct {
   877  	Serial        string            `db:"serial"`
   878  	Status        core.OCSPStatus   `db:"status"`
   879  	RevokedReason revocation.Reason `db:"revokedReason"`
   880  	RevokedDate   time.Time         `db:"revokedDate"`
   881  }
   882  
   883  // orderFQDNSet contains the SHA256 hash of the lowercased, comma joined names
   884  // from a new-order request, along with the corresponding orderID, the
   885  // registration ID, and the order expiry. This is used to find
   886  // existing orders for reuse.
   887  type orderFQDNSet struct {
   888  	ID             int64
   889  	SetHash        []byte
   890  	OrderID        int64
   891  	RegistrationID int64
   892  	Expires        time.Time
   893  }
   894  
   895  func addFQDNSet(ctx context.Context, db db.Inserter, names []string, serial string, issued time.Time, expires time.Time) error {
   896  	return db.Insert(ctx, &core.FQDNSet{
   897  		SetHash: core.HashNames(names),
   898  		Serial:  serial,
   899  		Issued:  issued,
   900  		Expires: expires,
   901  	})
   902  }
   903  
   904  // addOrderFQDNSet creates a new OrderFQDNSet row using the provided
   905  // information. This function accepts a transaction so that the orderFqdnSet
   906  // addition can take place within the order addition transaction. The caller is
   907  // required to rollback the transaction if an error is returned.
   908  func addOrderFQDNSet(
   909  	ctx context.Context,
   910  	db db.Inserter,
   911  	names []string,
   912  	orderID int64,
   913  	regID int64,
   914  	expires time.Time) error {
   915  	return db.Insert(ctx, &orderFQDNSet{
   916  		SetHash:        core.HashNames(names),
   917  		OrderID:        orderID,
   918  		RegistrationID: regID,
   919  		Expires:        expires,
   920  	})
   921  }
   922  
   923  // deleteOrderFQDNSet deletes a OrderFQDNSet row that matches the provided
   924  // orderID. This function accepts a transaction so that the deletion can
   925  // take place within the finalization transaction. The caller is required to
   926  // rollback the transaction if an error is returned.
   927  func deleteOrderFQDNSet(
   928  	ctx context.Context,
   929  	db db.Execer,
   930  	orderID int64) error {
   931  
   932  	result, err := db.ExecContext(ctx, `
   933  	  DELETE FROM orderFqdnSets
   934  		WHERE orderID = ?`,
   935  		orderID)
   936  	if err != nil {
   937  		return err
   938  	}
   939  	rowsDeleted, err := result.RowsAffected()
   940  	if err != nil {
   941  		return err
   942  	}
   943  	// We always expect there to be an order FQDN set row for each
   944  	// pending/processing order that is being finalized. If there isn't one then
   945  	// something is amiss and should be raised as an internal server error
   946  	if rowsDeleted == 0 {
   947  		return berrors.InternalServerError("No orderFQDNSet exists to delete")
   948  	}
   949  	return nil
   950  }
   951  
   952  func addIssuedNames(ctx context.Context, queryer db.Queryer, cert *x509.Certificate, isRenewal bool) error {
   953  	if len(cert.DNSNames) == 0 {
   954  		return berrors.InternalServerError("certificate has no DNSNames")
   955  	}
   956  
   957  	multiInserter, err := db.NewMultiInserter("issuedNames", []string{"reversedName", "serial", "notBefore", "renewal"}, "")
   958  	if err != nil {
   959  		return err
   960  	}
   961  	for _, name := range cert.DNSNames {
   962  		err = multiInserter.Add([]interface{}{
   963  			ReverseName(name),
   964  			core.SerialToString(cert.SerialNumber),
   965  			cert.NotBefore,
   966  			isRenewal,
   967  		})
   968  		if err != nil {
   969  			return err
   970  		}
   971  	}
   972  	_, err = multiInserter.Insert(ctx, queryer)
   973  	return err
   974  }
   975  
   976  func addKeyHash(ctx context.Context, db db.Inserter, cert *x509.Certificate) error {
   977  	if cert.RawSubjectPublicKeyInfo == nil {
   978  		return errors.New("certificate has a nil RawSubjectPublicKeyInfo")
   979  	}
   980  	h := sha256.Sum256(cert.RawSubjectPublicKeyInfo)
   981  	khm := &keyHashModel{
   982  		KeyHash:      h[:],
   983  		CertNotAfter: cert.NotAfter,
   984  		CertSerial:   core.SerialToString(cert.SerialNumber),
   985  	}
   986  	return db.Insert(ctx, khm)
   987  }
   988  
   989  var blockedKeysColumns = "keyHash, added, source, comment"
   990  
   991  // statusForOrder examines the status of a provided order's authorizations to
   992  // determine what the overall status of the order should be. In summary:
   993  //   - If the order has an error, the order is invalid
   994  //   - If any of the order's authorizations are in any state other than
   995  //     valid or pending, the order is invalid.
   996  //   - If any of the order's authorizations are pending, the order is pending.
   997  //   - If all of the order's authorizations are valid, and there is
   998  //     a certificate serial, the order is valid.
   999  //   - If all of the order's authorizations are valid, and we have began
  1000  //     processing, but there is no certificate serial, the order is processing.
  1001  //   - If all of the order's authorizations are valid, and we haven't begun
  1002  //     processing, then the order is status ready.
  1003  //
  1004  // An error is returned for any other case.
  1005  func statusForOrder(ctx context.Context, s db.Selector, order *corepb.Order, now time.Time) (string, error) {
  1006  	// Without any further work we know an order with an error is invalid
  1007  	if order.Error != nil {
  1008  		return string(core.StatusInvalid), nil
  1009  	}
  1010  
  1011  	// If the order is expired the status is invalid and we don't need to get
  1012  	// order authorizations. Its important to exit early in this case because an
  1013  	// order that references an expired authorization will be itself have been
  1014  	// expired (because we match the order expiry to the associated authz expiries
  1015  	// in ra.NewOrder), and expired authorizations may be purged from the DB.
  1016  	// Because of this purging fetching the authz's for an expired order may
  1017  	// return fewer authz objects than expected, triggering a 500 error response.
  1018  	orderExpiry := time.Unix(0, order.ExpiresNS)
  1019  	if orderExpiry.Before(now) {
  1020  		return string(core.StatusInvalid), nil
  1021  	}
  1022  
  1023  	// Get the full Authorization objects for the order
  1024  	authzValidityInfo, err := getAuthorizationStatuses(ctx, s, order.V2Authorizations)
  1025  	// If there was an error getting the authorizations, return it immediately
  1026  	if err != nil {
  1027  		return "", err
  1028  	}
  1029  
  1030  	// If getAuthorizationStatuses returned a different number of authorization
  1031  	// objects than the order's slice of authorization IDs something has gone
  1032  	// wrong worth raising an internal error about.
  1033  	if len(authzValidityInfo) != len(order.V2Authorizations) {
  1034  		return "", berrors.InternalServerError(
  1035  			"getAuthorizationStatuses returned the wrong number of authorization statuses "+
  1036  				"(%d vs expected %d) for order %d",
  1037  			len(authzValidityInfo), len(order.V2Authorizations), order.Id)
  1038  	}
  1039  
  1040  	// Keep a count of the authorizations seen
  1041  	pendingAuthzs := 0
  1042  	validAuthzs := 0
  1043  	otherAuthzs := 0
  1044  	expiredAuthzs := 0
  1045  
  1046  	// Loop over each of the order's authorization objects to examine the authz status
  1047  	for _, info := range authzValidityInfo {
  1048  		switch core.AcmeStatus(info.Status) {
  1049  		case core.StatusPending:
  1050  			pendingAuthzs++
  1051  		case core.StatusValid:
  1052  			validAuthzs++
  1053  		case core.StatusInvalid:
  1054  			otherAuthzs++
  1055  		case core.StatusDeactivated:
  1056  			otherAuthzs++
  1057  		case core.StatusRevoked:
  1058  			otherAuthzs++
  1059  		default:
  1060  			return "", berrors.InternalServerError(
  1061  				"Order is in an invalid state. Authz has invalid status %s",
  1062  				info.Status)
  1063  		}
  1064  		if info.Expires.Before(now) {
  1065  			expiredAuthzs++
  1066  		}
  1067  	}
  1068  
  1069  	// An order is invalid if **any** of its authzs are invalid, deactivated,
  1070  	// revoked, or expired, see https://tools.ietf.org/html/rfc8555#section-7.1.6
  1071  	if otherAuthzs > 0 || expiredAuthzs > 0 {
  1072  		return string(core.StatusInvalid), nil
  1073  	}
  1074  	// An order is pending if **any** of its authzs are pending
  1075  	if pendingAuthzs > 0 {
  1076  		return string(core.StatusPending), nil
  1077  	}
  1078  
  1079  	// An order is fully authorized if it has valid authzs for each of the order
  1080  	// names
  1081  	fullyAuthorized := len(order.Names) == validAuthzs
  1082  
  1083  	// If the order isn't fully authorized we've encountered an internal error:
  1084  	// Above we checked for any invalid or pending authzs and should have returned
  1085  	// early. Somehow we made it this far but also don't have the correct number
  1086  	// of valid authzs.
  1087  	if !fullyAuthorized {
  1088  		return "", berrors.InternalServerError(
  1089  			"Order has the incorrect number of valid authorizations & no pending, " +
  1090  				"deactivated or invalid authorizations")
  1091  	}
  1092  
  1093  	// If the order is fully authorized and the certificate serial is set then the
  1094  	// order is valid
  1095  	if fullyAuthorized && order.CertificateSerial != "" {
  1096  		return string(core.StatusValid), nil
  1097  	}
  1098  
  1099  	// If the order is fully authorized, and we have began processing it, then the
  1100  	// order is processing.
  1101  	if fullyAuthorized && order.BeganProcessing {
  1102  		return string(core.StatusProcessing), nil
  1103  	}
  1104  
  1105  	if fullyAuthorized && !order.BeganProcessing {
  1106  		return string(core.StatusReady), nil
  1107  	}
  1108  
  1109  	return "", berrors.InternalServerError(
  1110  		"Order %d is in an invalid state. No state known for this order's "+
  1111  			"authorizations", order.Id)
  1112  }
  1113  
  1114  type authzValidity struct {
  1115  	Status  string
  1116  	Expires time.Time
  1117  }
  1118  
  1119  // getAuthorizationStatuses takes a sequence of authz IDs, and returns the
  1120  // status and expiration date of each of them.
  1121  func getAuthorizationStatuses(ctx context.Context, s db.Selector, ids []int64) ([]authzValidity, error) {
  1122  	var params []interface{}
  1123  	for _, id := range ids {
  1124  		params = append(params, id)
  1125  	}
  1126  	var validityInfo []struct {
  1127  		Status  uint8
  1128  		Expires time.Time
  1129  	}
  1130  	_, err := s.Select(
  1131  		ctx,
  1132  		&validityInfo,
  1133  		fmt.Sprintf("SELECT status, expires FROM authz2 WHERE id IN (%s)",
  1134  			db.QuestionMarks(len(ids))),
  1135  		params...,
  1136  	)
  1137  	if err != nil {
  1138  		return nil, err
  1139  	}
  1140  
  1141  	allAuthzValidity := make([]authzValidity, len(validityInfo))
  1142  	for i, info := range validityInfo {
  1143  		allAuthzValidity[i] = authzValidity{
  1144  			Status:  string(uintToStatus[info.Status]),
  1145  			Expires: info.Expires,
  1146  		}
  1147  	}
  1148  	return allAuthzValidity, nil
  1149  }
  1150  
  1151  // authzForOrder retrieves the authorization IDs for an order.
  1152  func authzForOrder(ctx context.Context, s db.Selector, orderID int64) ([]int64, error) {
  1153  	var v2IDs []int64
  1154  	_, err := s.Select(
  1155  		ctx,
  1156  		&v2IDs,
  1157  		"SELECT authzID FROM orderToAuthz2 WHERE orderID = ?",
  1158  		orderID,
  1159  	)
  1160  	return v2IDs, err
  1161  }
  1162  
  1163  // namesForOrder finds all of the requested names associated with an order. The
  1164  // names are returned in their reversed form (see `sa.ReverseName`).
  1165  func namesForOrder(ctx context.Context, s db.Selector, orderID int64) ([]string, error) {
  1166  	var reversedNames []string
  1167  	_, err := s.Select(
  1168  		ctx,
  1169  		&reversedNames,
  1170  		`SELECT reversedName
  1171  	   FROM requestedNames
  1172  	   WHERE orderID = ?`,
  1173  		orderID)
  1174  	if err != nil {
  1175  		return nil, err
  1176  	}
  1177  	return reversedNames, nil
  1178  }
  1179  
  1180  // crlShardModel represents one row in the crlShards table. The ThisUpdate and
  1181  // NextUpdate fields are pointers because they are NULL-able columns.
  1182  type crlShardModel struct {
  1183  	ID          int64      `db:"id"`
  1184  	IssuerID    int64      `db:"issuerID"`
  1185  	Idx         int        `db:"idx"`
  1186  	ThisUpdate  *time.Time `db:"thisUpdate"`
  1187  	NextUpdate  *time.Time `db:"nextUpdate"`
  1188  	LeasedUntil time.Time  `db:"leasedUntil"`
  1189  }
  1190  
  1191  // revokedCertModel represents one row in the revokedCertificates table. It
  1192  // contains all of the information necessary to populate a CRL entry or OCSP
  1193  // response for the indicated certificate.
  1194  type revokedCertModel struct {
  1195  	ID            int64             `db:"id"`
  1196  	IssuerID      int64             `db:"issuerID"`
  1197  	Serial        string            `db:"serial"`
  1198  	NotAfterHour  time.Time         `db:"notAfterHour"`
  1199  	ShardIdx      int64             `db:"shardIdx"`
  1200  	RevokedDate   time.Time         `db:"revokedDate"`
  1201  	RevokedReason revocation.Reason `db:"revokedReason"`
  1202  }
  1203  

View as plain text