429 lines
11 KiB
Go
429 lines
11 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 core
|
|
|
|
import (
|
|
"crypto"
|
|
"crypto/ecdsa"
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/sha1"
|
|
"crypto/sha256"
|
|
"crypto/sha512"
|
|
"crypto/x509"
|
|
"encoding/asn1"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"encoding/pem"
|
|
"errors"
|
|
"fmt"
|
|
"hash"
|
|
"io"
|
|
"io/ioutil"
|
|
"math/big"
|
|
"net/url"
|
|
"strings"
|
|
|
|
jose "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose"
|
|
blog "github.com/letsencrypt/boulder/log"
|
|
)
|
|
|
|
// Package Variables Variables
|
|
|
|
// BuildID is set by the compiler (using -ldflags "-X core.BuildID $(git rev-parse --short HEAD)")
|
|
// and is used by GetBuildID
|
|
var BuildID string
|
|
|
|
// BuildHost is set by the compiler and is used by GetBuildHost
|
|
var BuildHost string
|
|
|
|
// BuildTime is set by the compiler and is used by GetBuildTime
|
|
var BuildTime string
|
|
|
|
// Errors
|
|
|
|
// InternalServerError indicates that something has gone wrong unrelated to the
|
|
// user's input, and will be considered by the Load Balancer as an indication
|
|
// that this Boulder instance may be malfunctioning. Minimally, returning this
|
|
// will cause an error page to be generated at the CDN/LB for the client.
|
|
// Consequently, you should only use this error when Boulder's internal
|
|
// constraints have been violated.
|
|
type InternalServerError string
|
|
|
|
// NotSupportedError indicates a method is not yet supported
|
|
type NotSupportedError string
|
|
|
|
// MalformedRequestError indicates the user data was improper
|
|
type MalformedRequestError string
|
|
|
|
// UnauthorizedError indicates the user did not satisfactorily prove identity
|
|
type UnauthorizedError string
|
|
|
|
// NotFoundError indicates the destination was unknown. Whoa oh oh ohhh.
|
|
type NotFoundError string
|
|
|
|
// LengthRequiredError indicates a POST was sent with no Content-Length.
|
|
type LengthRequiredError string
|
|
|
|
// SyntaxError indicates the user improperly formatted their data.
|
|
type SyntaxError string
|
|
|
|
// SignatureValidationError indicates that the user's signature could not
|
|
// be verified, either through adversarial activity, or misconfiguration of
|
|
// the user client.
|
|
type SignatureValidationError string
|
|
|
|
// CertificateIssuanceError indicates the certificate failed to be issued
|
|
// for some reason.
|
|
type CertificateIssuanceError string
|
|
|
|
// NoSuchRegistrationError indicates that a registration could not be found.
|
|
type NoSuchRegistrationError string
|
|
|
|
// RateLimitedError indicates the user has hit a rate limit
|
|
type RateLimitedError string
|
|
|
|
// TooManyRPCRequestsError indicates an RPC server has hit it's concurrent request
|
|
// limit
|
|
type TooManyRPCRequestsError string
|
|
|
|
func (e InternalServerError) Error() string { return string(e) }
|
|
func (e NotSupportedError) Error() string { return string(e) }
|
|
func (e MalformedRequestError) Error() string { return string(e) }
|
|
func (e UnauthorizedError) Error() string { return string(e) }
|
|
func (e NotFoundError) Error() string { return string(e) }
|
|
func (e LengthRequiredError) Error() string { return string(e) }
|
|
func (e SyntaxError) Error() string { return string(e) }
|
|
func (e SignatureValidationError) Error() string { return string(e) }
|
|
func (e CertificateIssuanceError) Error() string { return string(e) }
|
|
func (e NoSuchRegistrationError) Error() string { return string(e) }
|
|
func (e RateLimitedError) Error() string { return string(e) }
|
|
func (e TooManyRPCRequestsError) Error() string { return string(e) }
|
|
|
|
// Base64 functions
|
|
|
|
func pad(x string) string {
|
|
switch len(x) % 4 {
|
|
case 2:
|
|
return x + "=="
|
|
case 3:
|
|
return x + "="
|
|
}
|
|
return x
|
|
}
|
|
|
|
func unpad(x string) string {
|
|
end := len(x)
|
|
for end != 0 && x[end-1] == '=' {
|
|
end--
|
|
}
|
|
return x[:end]
|
|
}
|
|
|
|
// B64enc encodes a byte array as unpadded, URL-safe Base64
|
|
func B64enc(x []byte) string {
|
|
return unpad(base64.URLEncoding.EncodeToString(x))
|
|
}
|
|
|
|
// B64dec decodes a byte array from unpadded, URL-safe Base64
|
|
func B64dec(x string) ([]byte, error) {
|
|
return base64.URLEncoding.DecodeString(pad(x))
|
|
}
|
|
|
|
// Random stuff
|
|
|
|
// RandomString returns a randomly generated string of the requested length.
|
|
func RandomString(byteLength int) string {
|
|
b := make([]byte, byteLength)
|
|
_, err := io.ReadFull(rand.Reader, b)
|
|
if err != nil {
|
|
ohdear := "RandomString entropy failure? " + err.Error()
|
|
logger := blog.GetAuditLogger()
|
|
logger.EmergencyExit(ohdear)
|
|
}
|
|
return B64enc(b)
|
|
}
|
|
|
|
// NewToken produces a random string for Challenges, etc.
|
|
func NewToken() string {
|
|
return RandomString(32)
|
|
}
|
|
|
|
// Fingerprints
|
|
|
|
// Fingerprint256 produces an unpadded, URL-safe Base64-encoded SHA256 digest
|
|
// of the data.
|
|
func Fingerprint256(data []byte) string {
|
|
d := sha256.New()
|
|
_, _ = d.Write(data) // Never returns an error
|
|
return B64enc(d.Sum(nil))
|
|
}
|
|
|
|
// KeyDigest produces a padded, standard Base64-encoded SHA256 digest of a
|
|
// provided public key.
|
|
func KeyDigest(key crypto.PublicKey) (string, error) {
|
|
switch t := key.(type) {
|
|
case *jose.JsonWebKey:
|
|
if t == nil {
|
|
return "", fmt.Errorf("Cannot compute digest of nil key")
|
|
}
|
|
return KeyDigest(t.Key)
|
|
case jose.JsonWebKey:
|
|
return KeyDigest(t.Key)
|
|
default:
|
|
keyDER, err := x509.MarshalPKIXPublicKey(key)
|
|
if err != nil {
|
|
logger := blog.GetAuditLogger()
|
|
logger.Debug(fmt.Sprintf("Problem marshaling public key: %s", err))
|
|
return "", err
|
|
}
|
|
spkiDigest := sha256.Sum256(keyDER)
|
|
return base64.StdEncoding.EncodeToString(spkiDigest[0:32]), nil
|
|
}
|
|
}
|
|
|
|
// KeyDigestEquals determines whether two public keys have the same digest.
|
|
func KeyDigestEquals(j, k crypto.PublicKey) bool {
|
|
digestJ, errJ := KeyDigest(j)
|
|
digestK, errK := KeyDigest(k)
|
|
// Keys that don't have a valid digest (due to marshalling problems)
|
|
// are never equal. So, e.g. nil keys are not equal.
|
|
if errJ != nil || errK != nil {
|
|
return false
|
|
}
|
|
return digestJ == digestK
|
|
}
|
|
|
|
// AcmeURL is a URL that automatically marshal/unmarshal to JSON strings
|
|
type AcmeURL url.URL
|
|
|
|
func ParseAcmeURL(s string) (*AcmeURL, error) {
|
|
u, err := url.Parse(s)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return (*AcmeURL)(u), nil
|
|
}
|
|
|
|
func (u *AcmeURL) String() string {
|
|
uu := (*url.URL)(u)
|
|
return uu.String()
|
|
}
|
|
|
|
// PathSegments splits an AcmeURL into segments on the '/' characters
|
|
func (u *AcmeURL) PathSegments() (segments []string) {
|
|
segments = strings.Split(u.Path, "/")
|
|
if len(segments) > 0 && len(segments[0]) == 0 {
|
|
segments = segments[1:]
|
|
}
|
|
return
|
|
}
|
|
|
|
// MarshalJSON encodes an AcmeURL for transfer
|
|
func (u *AcmeURL) MarshalJSON() ([]byte, error) {
|
|
return json.Marshal(u.String())
|
|
}
|
|
|
|
// UnmarshalJSON decodes an AcmeURL from transfer
|
|
func (u *AcmeURL) UnmarshalJSON(data []byte) error {
|
|
var str string
|
|
if err := json.Unmarshal(data, &str); err != nil {
|
|
return err
|
|
}
|
|
|
|
uu, err := url.Parse(str)
|
|
*u = AcmeURL(*uu)
|
|
return err
|
|
}
|
|
|
|
// VerifyCSR verifies that a Certificate Signature Request is well-formed.
|
|
//
|
|
// Note: this is the missing CertificateRequest.Verify() method
|
|
func VerifyCSR(csr *x509.CertificateRequest) error {
|
|
// Compute the hash of the TBSCertificateRequest
|
|
var hashID crypto.Hash
|
|
var hash hash.Hash
|
|
switch csr.SignatureAlgorithm {
|
|
case x509.SHA1WithRSA:
|
|
fallthrough
|
|
case x509.ECDSAWithSHA1:
|
|
hashID = crypto.SHA1
|
|
hash = sha1.New()
|
|
case x509.SHA256WithRSA:
|
|
fallthrough
|
|
case x509.ECDSAWithSHA256:
|
|
hashID = crypto.SHA256
|
|
hash = sha256.New()
|
|
case x509.SHA384WithRSA:
|
|
fallthrough
|
|
case x509.ECDSAWithSHA384:
|
|
hashID = crypto.SHA384
|
|
hash = sha512.New384()
|
|
case x509.SHA512WithRSA:
|
|
fallthrough
|
|
case x509.ECDSAWithSHA512:
|
|
hashID = crypto.SHA512
|
|
hash = sha512.New()
|
|
default:
|
|
return errors.New("Unsupported CSR signing algorithm")
|
|
}
|
|
_, _ = hash.Write(csr.RawTBSCertificateRequest) // Never returns an error
|
|
inputHash := hash.Sum(nil)
|
|
|
|
// Verify the signature using the public key in the CSR
|
|
switch csr.SignatureAlgorithm {
|
|
case x509.SHA1WithRSA:
|
|
fallthrough
|
|
case x509.SHA256WithRSA:
|
|
fallthrough
|
|
case x509.SHA384WithRSA:
|
|
fallthrough
|
|
case x509.SHA512WithRSA:
|
|
rsaKey := csr.PublicKey.(*rsa.PublicKey)
|
|
return rsa.VerifyPKCS1v15(rsaKey, hashID, inputHash, csr.Signature)
|
|
case x509.ECDSAWithSHA1:
|
|
fallthrough
|
|
case x509.ECDSAWithSHA256:
|
|
fallthrough
|
|
case x509.ECDSAWithSHA384:
|
|
fallthrough
|
|
case x509.ECDSAWithSHA512:
|
|
ecKey := csr.PublicKey.(*ecdsa.PublicKey)
|
|
|
|
var sig struct{ R, S *big.Int }
|
|
_, err := asn1.Unmarshal(csr.Signature, &sig)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if ecdsa.Verify(ecKey, inputHash, sig.R, sig.S) {
|
|
return nil
|
|
}
|
|
|
|
return errors.New("Invalid ECDSA signature on CSR")
|
|
}
|
|
|
|
return errors.New("Unsupported CSR signing algorithm")
|
|
}
|
|
|
|
// SerialToString converts a certificate serial number (big.Int) to a String
|
|
// consistently.
|
|
func SerialToString(serial *big.Int) string {
|
|
return fmt.Sprintf("%036x", serial)
|
|
}
|
|
|
|
// StringToSerial converts a string into a certificate serial number (big.Int)
|
|
// consistently.
|
|
func StringToSerial(serial string) (*big.Int, error) {
|
|
var serialNum big.Int
|
|
if !ValidSerial(serial) {
|
|
return &serialNum, errors.New("Invalid serial number")
|
|
}
|
|
_, err := fmt.Sscanf(serial, "%036x", &serialNum)
|
|
return &serialNum, err
|
|
}
|
|
|
|
func ValidSerial(serial string) bool {
|
|
// Originally, serial numbers were 32 hex characters long. We later increased
|
|
// them to 36, but we allow the shorter ones because they exist in some
|
|
// production databases.
|
|
if len(serial) < 32 && len(serial) > 36 {
|
|
return false
|
|
}
|
|
_, err := hex.DecodeString(serial)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// GetBuildID identifies what build is running.
|
|
func GetBuildID() (retID string) {
|
|
retID = BuildID
|
|
if retID == "" {
|
|
retID = "Unspecified"
|
|
}
|
|
return
|
|
}
|
|
|
|
// GetBuildTime identifies when this build was made
|
|
func GetBuildTime() (retID string) {
|
|
retID = BuildTime
|
|
if retID == "" {
|
|
retID = "Unspecified"
|
|
}
|
|
return
|
|
}
|
|
|
|
// GetBuildHost identifies the building host
|
|
func GetBuildHost() (retID string) {
|
|
retID = BuildHost
|
|
if retID == "" {
|
|
retID = "Unspecified"
|
|
}
|
|
return
|
|
}
|
|
|
|
// UniqueNames returns the set of all unique names in the input.
|
|
func UniqueNames(names []string) (unique []string) {
|
|
nameMap := make(map[string]int, len(names))
|
|
for _, name := range names {
|
|
nameMap[name] = 1
|
|
}
|
|
|
|
unique = make([]string, 0, len(nameMap))
|
|
for name := range nameMap {
|
|
unique = append(unique, name)
|
|
}
|
|
return
|
|
}
|
|
|
|
// LoadCertBundle loads a PEM bundle of certificates from disk
|
|
func LoadCertBundle(filename string) ([]*x509.Certificate, error) {
|
|
bundleBytes, err := ioutil.ReadFile(filename)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var bundle []*x509.Certificate
|
|
var block *pem.Block
|
|
rest := bundleBytes
|
|
for {
|
|
block, rest = pem.Decode(rest)
|
|
if block == nil {
|
|
break
|
|
}
|
|
if block.Type != "CERTIFICATE" {
|
|
return nil, fmt.Errorf("Block has invalid type: %s", block.Type)
|
|
}
|
|
cert, err := x509.ParseCertificate(block.Bytes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bundle = append(bundle, cert)
|
|
}
|
|
|
|
if len(bundle) == 0 {
|
|
return nil, fmt.Errorf("Bundle doesn't contain any certificates")
|
|
}
|
|
|
|
return bundle, nil
|
|
}
|
|
|
|
// LoadCert loads a PEM certificate specified by filename or returns a error
|
|
func LoadCert(filename string) (cert *x509.Certificate, err error) {
|
|
certPEM, err := ioutil.ReadFile(filename)
|
|
if err != nil {
|
|
return
|
|
}
|
|
block, _ := pem.Decode(certPEM)
|
|
if block == nil {
|
|
return nil, fmt.Errorf("No data in cert PEM file %s", filename)
|
|
}
|
|
cert, err = x509.ParseCertificate(block.Bytes)
|
|
return
|
|
}
|