560 lines
14 KiB
Go
560 lines
14 KiB
Go
// Copyright 2014 ISRG. All rights reserved
|
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
package sa
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"crypto/x509"
|
|
"database/sql"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
jose "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose"
|
|
gorp "github.com/letsencrypt/boulder/Godeps/_workspace/src/gopkg.in/gorp.v1"
|
|
"github.com/letsencrypt/boulder/core"
|
|
blog "github.com/letsencrypt/boulder/log"
|
|
)
|
|
|
|
// SQLStorageAuthority defines a Storage Authority
|
|
type SQLStorageAuthority struct {
|
|
dbMap *gorp.DbMap
|
|
log *blog.AuditLogger
|
|
}
|
|
|
|
func digest256(data []byte) []byte {
|
|
d := sha256.New()
|
|
_, _ = d.Write(data) // Never returns an error
|
|
return d.Sum(nil)
|
|
}
|
|
|
|
// Utility models
|
|
type pendingauthzModel struct {
|
|
core.Authorization
|
|
|
|
LockCol int64
|
|
}
|
|
|
|
type authzModel struct {
|
|
core.Authorization
|
|
|
|
Sequence int64 `db:"sequence"`
|
|
}
|
|
|
|
// NewSQLStorageAuthority provides persistence using a SQL backend for
|
|
// Boulder. It will modify the given gorp.DbMap by adding relevent tables.
|
|
func NewSQLStorageAuthority(dbMap *gorp.DbMap) (*SQLStorageAuthority, error) {
|
|
logger := blog.GetAuditLogger()
|
|
|
|
logger.Notice("Storage Authority Starting")
|
|
|
|
ssa := &SQLStorageAuthority{
|
|
dbMap: dbMap,
|
|
log: logger,
|
|
}
|
|
|
|
return ssa, nil
|
|
}
|
|
|
|
// SetSQLDebug enables/disables GORP SQL-level Debugging
|
|
func (ssa *SQLStorageAuthority) SetSQLDebug(state bool) {
|
|
SetSQLDebug(ssa.dbMap, state)
|
|
}
|
|
|
|
// CreateTablesIfNotExists instructs the ORM to create any missing tables.
|
|
func (ssa *SQLStorageAuthority) CreateTablesIfNotExists() (err error) {
|
|
err = ssa.dbMap.CreateTablesIfNotExists()
|
|
return
|
|
}
|
|
|
|
func statusIsPending(status core.AcmeStatus) bool {
|
|
return status == core.StatusPending || status == core.StatusProcessing || status == core.StatusUnknown
|
|
}
|
|
|
|
func existingPending(tx *gorp.Transaction, id string) bool {
|
|
var count int64
|
|
_ = tx.SelectOne(&count, "SELECT count(*) FROM pending_authz WHERE id = :id", map[string]interface{}{"id": id})
|
|
return count > 0
|
|
}
|
|
|
|
func existingFinal(tx *gorp.Transaction, id string) bool {
|
|
var count int64
|
|
_ = tx.SelectOne(&count, "SELECT count(*) FROM authz WHERE id = :id", map[string]interface{}{"id": id})
|
|
return count > 0
|
|
}
|
|
|
|
func existingRegistration(tx *gorp.Transaction, id int64) bool {
|
|
var count int64
|
|
_ = tx.SelectOne(&count, "SELECT count(*) FROM registrations WHERE id = :id", map[string]interface{}{"id": id})
|
|
return count > 0
|
|
}
|
|
|
|
type NoSuchRegistrationError struct {
|
|
Msg string
|
|
}
|
|
|
|
func (e NoSuchRegistrationError) Error() string {
|
|
return e.Msg
|
|
}
|
|
|
|
// GetRegistration obtains a Registration by ID
|
|
func (ssa *SQLStorageAuthority) GetRegistration(id int64) (core.Registration, error) {
|
|
regObj, err := ssa.dbMap.Get(regModel{}, id)
|
|
if err != nil {
|
|
return core.Registration{}, err
|
|
}
|
|
if regObj == nil {
|
|
msg := fmt.Sprintf("No registrations with ID %d", id)
|
|
return core.Registration{}, NoSuchRegistrationError{Msg: msg}
|
|
}
|
|
regPtr, ok := regObj.(*regModel)
|
|
if !ok {
|
|
return core.Registration{}, fmt.Errorf("Invalid cast to reg model object")
|
|
}
|
|
return modelToRegistration(regPtr)
|
|
}
|
|
|
|
// GetRegistrationByKey obtains a Registration by JWK
|
|
func (ssa *SQLStorageAuthority) GetRegistrationByKey(key jose.JsonWebKey) (core.Registration, error) {
|
|
reg := ®Model{}
|
|
sha, err := core.KeyDigest(key.Key)
|
|
if err != nil {
|
|
return core.Registration{}, err
|
|
}
|
|
err = ssa.dbMap.SelectOne(reg, "SELECT * FROM registrations WHERE jwk_sha256 = :key", map[string]interface{}{"key": sha})
|
|
|
|
if err == sql.ErrNoRows {
|
|
msg := fmt.Sprintf("No registrations with public key sha256 %s", sha)
|
|
return core.Registration{}, NoSuchRegistrationError{Msg: msg}
|
|
}
|
|
if err != nil {
|
|
return core.Registration{}, err
|
|
}
|
|
|
|
return modelToRegistration(reg)
|
|
}
|
|
|
|
// GetAuthorization obtains an Authorization by ID
|
|
func (ssa *SQLStorageAuthority) GetAuthorization(id string) (authz core.Authorization, err error) {
|
|
tx, err := ssa.dbMap.Begin()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
authObj, err := tx.Get(pendingauthzModel{}, id)
|
|
if err != nil {
|
|
tx.Rollback()
|
|
return
|
|
}
|
|
if authObj == nil {
|
|
authObj, err = tx.Get(authzModel{}, id)
|
|
if err != nil {
|
|
tx.Rollback()
|
|
return
|
|
}
|
|
if authObj == nil {
|
|
err = fmt.Errorf("No pending_authz or authz with ID %s", id)
|
|
tx.Rollback()
|
|
return
|
|
}
|
|
authD := authObj.(*authzModel)
|
|
authz = authD.Authorization
|
|
|
|
err = tx.Commit()
|
|
return
|
|
}
|
|
authD := *authObj.(*pendingauthzModel)
|
|
authz = authD.Authorization
|
|
|
|
err = tx.Commit()
|
|
return
|
|
}
|
|
|
|
// Get the valid authorization with biggest expire date for a given domain and registrationId
|
|
func (ssa *SQLStorageAuthority) GetLatestValidAuthorization(registrationId int64, identifier core.AcmeIdentifier) (authz core.Authorization, err error) {
|
|
ident, err := json.Marshal(identifier)
|
|
if err != nil {
|
|
return
|
|
}
|
|
err = ssa.dbMap.SelectOne(&authz, "SELECT id, identifier, registrationID, status, expires, challenges, combinations "+
|
|
"FROM authz "+
|
|
"WHERE identifier = :identifier AND registrationID = :registrationId AND status = 'valid' "+
|
|
"ORDER BY expires DESC LIMIT 1",
|
|
map[string]interface{}{"identifier": string(ident), "registrationId": registrationId})
|
|
return
|
|
}
|
|
|
|
// GetCertificateByShortSerial takes an id consisting of the first, sequential half of a
|
|
// serial number and returns the first certificate whose full serial number is
|
|
// lexically greater than that id. This allows clients to query on the known
|
|
// sequential half of our serial numbers to enumerate all certificates.
|
|
func (ssa *SQLStorageAuthority) GetCertificateByShortSerial(shortSerial string) (cert core.Certificate, err error) {
|
|
if len(shortSerial) != 16 {
|
|
err = errors.New("Invalid certificate short serial " + shortSerial)
|
|
return
|
|
}
|
|
|
|
err = ssa.dbMap.SelectOne(&cert, "SELECT * FROM certificates WHERE serial LIKE :shortSerial",
|
|
map[string]interface{}{"shortSerial": shortSerial + "%"})
|
|
return
|
|
}
|
|
|
|
// GetCertificate takes a serial number and returns the corresponding
|
|
// certificate, or error if it does not exist.
|
|
func (ssa *SQLStorageAuthority) GetCertificate(serial string) (core.Certificate, error) {
|
|
if len(serial) != 32 {
|
|
err := fmt.Errorf("Invalid certificate serial %s", serial)
|
|
return core.Certificate{}, err
|
|
}
|
|
|
|
certObj, err := ssa.dbMap.Get(core.Certificate{}, serial)
|
|
if err != nil {
|
|
return core.Certificate{}, err
|
|
}
|
|
if certObj == nil {
|
|
ssa.log.Debug(fmt.Sprintf("Nil cert for %s", serial))
|
|
return core.Certificate{}, fmt.Errorf("Certificate does not exist for %s", serial)
|
|
}
|
|
|
|
certPtr, ok := certObj.(*core.Certificate)
|
|
if !ok {
|
|
ssa.log.Debug("Failed to convert cert")
|
|
return core.Certificate{}, fmt.Errorf("Error converting certificate response for %s", serial)
|
|
}
|
|
return *certPtr, err
|
|
}
|
|
|
|
// GetCertificateStatus takes a hexadecimal string representing the full 128-bit serial
|
|
// number of a certificate and returns data about that certificate's current
|
|
// validity.
|
|
func (ssa *SQLStorageAuthority) GetCertificateStatus(serial string) (status core.CertificateStatus, err error) {
|
|
if len(serial) != 32 {
|
|
err = errors.New("Invalid certificate serial " + serial)
|
|
return
|
|
}
|
|
|
|
certificateStats, err := ssa.dbMap.Get(core.CertificateStatus{}, serial)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
status = *certificateStats.(*core.CertificateStatus)
|
|
return
|
|
}
|
|
|
|
// NewRegistration stores a new Registration
|
|
func (ssa *SQLStorageAuthority) NewRegistration(reg core.Registration) (core.Registration, error) {
|
|
rm, err := registrationToModel(®)
|
|
if err != nil {
|
|
return reg, err
|
|
}
|
|
err = ssa.dbMap.Insert(rm)
|
|
if err != nil {
|
|
return reg, err
|
|
}
|
|
return modelToRegistration(rm)
|
|
}
|
|
|
|
// UpdateOCSP stores an updated OCSP response.
|
|
func (ssa *SQLStorageAuthority) UpdateOCSP(serial string, ocspResponse []byte) (err error) {
|
|
status, err := ssa.GetCertificateStatus(serial)
|
|
if err != nil {
|
|
return fmt.Errorf(
|
|
"Unable to update OCSP for certificate %s: cert status not found.", serial)
|
|
}
|
|
|
|
tx, err := ssa.dbMap.Begin()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
timeStamp := time.Now()
|
|
|
|
// Record the response.
|
|
ocspResp := &core.OCSPResponse{Serial: serial, CreatedAt: timeStamp, Response: ocspResponse}
|
|
err = tx.Insert(ocspResp)
|
|
if err != nil {
|
|
tx.Rollback()
|
|
return err
|
|
}
|
|
|
|
// Reset the update clock
|
|
status.OCSPLastUpdated = timeStamp
|
|
_, err = tx.Update(&status)
|
|
if err != nil {
|
|
tx.Rollback()
|
|
return err
|
|
}
|
|
|
|
err = tx.Commit()
|
|
return
|
|
}
|
|
|
|
// MarkCertificateRevoked stores the fact that a certificate is revoked, along
|
|
// with a timestamp and a reason.
|
|
func (ssa *SQLStorageAuthority) MarkCertificateRevoked(serial string, ocspResponse []byte, reasonCode int) (err error) {
|
|
if _, err = ssa.GetCertificate(serial); err != nil {
|
|
return fmt.Errorf(
|
|
"Unable to mark certificate %s revoked: cert not found.", serial)
|
|
}
|
|
|
|
if _, err = ssa.GetCertificateStatus(serial); err != nil {
|
|
return fmt.Errorf(
|
|
"Unable to mark certificate %s revoked: cert status not found.", serial)
|
|
}
|
|
|
|
tx, err := ssa.dbMap.Begin()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
ocspResp := &core.OCSPResponse{Serial: serial, CreatedAt: time.Now(), Response: ocspResponse}
|
|
err = tx.Insert(ocspResp)
|
|
if err != nil {
|
|
tx.Rollback()
|
|
return
|
|
}
|
|
|
|
statusObj, err := tx.Get(core.CertificateStatus{}, serial)
|
|
if err != nil {
|
|
tx.Rollback()
|
|
return
|
|
}
|
|
if statusObj == nil {
|
|
err = fmt.Errorf("No certificate with serial %s", serial)
|
|
tx.Rollback()
|
|
return
|
|
}
|
|
status := statusObj.(*core.CertificateStatus)
|
|
status.Status = core.OCSPStatusRevoked
|
|
status.RevokedDate = time.Now()
|
|
status.RevokedReason = reasonCode
|
|
|
|
_, err = tx.Update(status)
|
|
if err != nil {
|
|
tx.Rollback()
|
|
return
|
|
}
|
|
|
|
err = tx.Commit()
|
|
return
|
|
}
|
|
|
|
// UpdateRegistration stores an updated Registration
|
|
func (ssa *SQLStorageAuthority) UpdateRegistration(reg core.Registration) error {
|
|
rm, err := registrationToModel(®)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
n, err := ssa.dbMap.Update(rm)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if n == 0 {
|
|
msg := fmt.Sprintf("Requested registration not found %v", reg.ID)
|
|
return NoSuchRegistrationError{Msg: msg}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// NewPendingAuthorization stores a new Pending Authorization
|
|
func (ssa *SQLStorageAuthority) NewPendingAuthorization(authz core.Authorization) (output core.Authorization, err error) {
|
|
tx, err := ssa.dbMap.Begin()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// Check that it doesn't exist already
|
|
authz.ID = core.NewToken()
|
|
for existingPending(tx, authz.ID) || existingFinal(tx, authz.ID) {
|
|
authz.ID = core.NewToken()
|
|
}
|
|
|
|
// Insert a stub row in pending
|
|
pendingAuthz := pendingauthzModel{Authorization: authz}
|
|
err = tx.Insert(&pendingAuthz)
|
|
if err != nil {
|
|
tx.Rollback()
|
|
return
|
|
}
|
|
|
|
err = tx.Commit()
|
|
output = pendingAuthz.Authorization
|
|
return
|
|
}
|
|
|
|
// UpdatePendingAuthorization updates a Pending Authorization
|
|
func (ssa *SQLStorageAuthority) UpdatePendingAuthorization(authz core.Authorization) (err error) {
|
|
tx, err := ssa.dbMap.Begin()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
if !statusIsPending(authz.Status) {
|
|
err = errors.New("Use FinalizeAuthorization() to update to a final status")
|
|
tx.Rollback()
|
|
return
|
|
}
|
|
|
|
if existingFinal(tx, authz.ID) {
|
|
err = errors.New("Cannot update a final authorization")
|
|
tx.Rollback()
|
|
return
|
|
}
|
|
|
|
if !existingPending(tx, authz.ID) {
|
|
err = errors.New("Requested authorization not found " + authz.ID)
|
|
tx.Rollback()
|
|
return
|
|
}
|
|
|
|
authObj, err := tx.Get(pendingauthzModel{}, authz.ID)
|
|
if err != nil {
|
|
tx.Rollback()
|
|
return
|
|
}
|
|
auth := authObj.(*pendingauthzModel)
|
|
auth.Authorization = authz
|
|
_, err = tx.Update(auth)
|
|
if err != nil {
|
|
tx.Rollback()
|
|
return
|
|
}
|
|
|
|
err = tx.Commit()
|
|
return
|
|
}
|
|
|
|
// FinalizeAuthorization converts a Pending Authorization to a final one
|
|
func (ssa *SQLStorageAuthority) FinalizeAuthorization(authz core.Authorization) (err error) {
|
|
tx, err := ssa.dbMap.Begin()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// Check that a pending authz exists
|
|
if !existingPending(tx, authz.ID) {
|
|
err = errors.New("Cannot finalize a authorization that is not pending")
|
|
tx.Rollback()
|
|
return
|
|
}
|
|
if statusIsPending(authz.Status) {
|
|
err = errors.New("Cannot finalize to a non-final status")
|
|
tx.Rollback()
|
|
return
|
|
}
|
|
|
|
// Manually set the index, to avoid AUTOINCREMENT issues
|
|
var sequence int64
|
|
sequenceObj, err := tx.SelectNullInt("SELECT max(sequence) FROM authz")
|
|
switch {
|
|
case !sequenceObj.Valid:
|
|
sequence = 0
|
|
case err != nil:
|
|
return
|
|
default:
|
|
sequence += sequenceObj.Int64 + 1
|
|
}
|
|
|
|
auth := &authzModel{authz, sequence}
|
|
authObj, err := tx.Get(pendingauthzModel{}, authz.ID)
|
|
if err != nil {
|
|
tx.Rollback()
|
|
return
|
|
}
|
|
oldAuth := authObj.(*pendingauthzModel)
|
|
|
|
err = tx.Insert(auth)
|
|
if err != nil {
|
|
tx.Rollback()
|
|
return
|
|
}
|
|
|
|
_, err = tx.Delete(oldAuth)
|
|
if err != nil {
|
|
tx.Rollback()
|
|
return
|
|
}
|
|
|
|
err = tx.Commit()
|
|
return
|
|
}
|
|
|
|
// AddCertificate stores an issued certificate.
|
|
func (ssa *SQLStorageAuthority) AddCertificate(certDER []byte, regID int64) (digest string, err error) {
|
|
var parsedCertificate *x509.Certificate
|
|
parsedCertificate, err = x509.ParseCertificate(certDER)
|
|
if err != nil {
|
|
return
|
|
}
|
|
digest = core.Fingerprint256(certDER)
|
|
serial := core.SerialToString(parsedCertificate.SerialNumber)
|
|
|
|
cert := &core.Certificate{
|
|
RegistrationID: regID,
|
|
Serial: serial,
|
|
Digest: digest,
|
|
DER: certDER,
|
|
Issued: time.Now(),
|
|
Expires: parsedCertificate.NotAfter,
|
|
}
|
|
certStatus := &core.CertificateStatus{
|
|
SubscriberApproved: false,
|
|
Status: core.OCSPStatus("good"),
|
|
OCSPLastUpdated: time.Time{},
|
|
Serial: serial,
|
|
RevokedDate: time.Time{},
|
|
RevokedReason: 0,
|
|
LockCol: 0,
|
|
}
|
|
|
|
tx, err := ssa.dbMap.Begin()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// TODO Verify that the serial number doesn't yet exist
|
|
err = tx.Insert(cert)
|
|
if err != nil {
|
|
tx.Rollback()
|
|
return
|
|
}
|
|
|
|
err = tx.Insert(certStatus)
|
|
if err != nil {
|
|
tx.Rollback()
|
|
return
|
|
}
|
|
|
|
err = tx.Commit()
|
|
return
|
|
}
|
|
|
|
// AlreadyDeniedCSR queries to find if the name list has already been denied.
|
|
func (ssa *SQLStorageAuthority) AlreadyDeniedCSR(names []string) (already bool, err error) {
|
|
sort.Strings(names)
|
|
|
|
var denied int64
|
|
err = ssa.dbMap.SelectOne(
|
|
&denied,
|
|
"SELECT count(*) FROM deniedCSRs WHERE names = :names",
|
|
map[string]interface{}{"names": strings.ToLower(strings.Join(names, ","))},
|
|
)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if denied > 0 {
|
|
already = true
|
|
}
|
|
|
|
return
|
|
}
|