...

Source file src/github.com/letsencrypt/boulder/core/util.go

Documentation: github.com/letsencrypt/boulder/core

     1  package core
     2  
     3  import (
     4  	"crypto"
     5  	"crypto/ecdsa"
     6  	"crypto/rand"
     7  	"crypto/rsa"
     8  	"crypto/sha256"
     9  	"crypto/x509"
    10  	"encoding/base64"
    11  	"encoding/hex"
    12  	"encoding/pem"
    13  	"errors"
    14  	"expvar"
    15  	"fmt"
    16  	"io"
    17  	"math/big"
    18  	mrand "math/rand"
    19  	"os"
    20  	"path"
    21  	"reflect"
    22  	"regexp"
    23  	"sort"
    24  	"strings"
    25  	"time"
    26  	"unicode"
    27  
    28  	"gopkg.in/go-jose/go-jose.v2"
    29  )
    30  
    31  const Unspecified = "Unspecified"
    32  
    33  // Package Variables Variables
    34  
    35  // BuildID is set by the compiler (using -ldflags "-X core.BuildID $(git rev-parse --short HEAD)")
    36  // and is used by GetBuildID
    37  var BuildID string
    38  
    39  // BuildHost is set by the compiler and is used by GetBuildHost
    40  var BuildHost string
    41  
    42  // BuildTime is set by the compiler and is used by GetBuildTime
    43  var BuildTime string
    44  
    45  func init() {
    46  	expvar.NewString("BuildID").Set(BuildID)
    47  	expvar.NewString("BuildTime").Set(BuildTime)
    48  }
    49  
    50  // Random stuff
    51  
    52  type randSource interface {
    53  	Read(p []byte) (n int, err error)
    54  }
    55  
    56  // RandReader is used so that it can be replaced in tests that require
    57  // deterministic output
    58  var RandReader randSource = rand.Reader
    59  
    60  // RandomString returns a randomly generated string of the requested length.
    61  func RandomString(byteLength int) string {
    62  	b := make([]byte, byteLength)
    63  	_, err := io.ReadFull(RandReader, b)
    64  	if err != nil {
    65  		panic(fmt.Sprintf("Error reading random bytes: %s", err))
    66  	}
    67  	return base64.RawURLEncoding.EncodeToString(b)
    68  }
    69  
    70  // NewToken produces a random string for Challenges, etc.
    71  func NewToken() string {
    72  	return RandomString(32)
    73  }
    74  
    75  var tokenFormat = regexp.MustCompile(`^[\w-]{43}$`)
    76  
    77  // LooksLikeAToken checks whether a string represents a 32-octet value in
    78  // the URL-safe base64 alphabet.
    79  func LooksLikeAToken(token string) bool {
    80  	return tokenFormat.MatchString(token)
    81  }
    82  
    83  // Fingerprints
    84  
    85  // Fingerprint256 produces an unpadded, URL-safe Base64-encoded SHA256 digest
    86  // of the data.
    87  func Fingerprint256(data []byte) string {
    88  	d := sha256.New()
    89  	_, _ = d.Write(data) // Never returns an error
    90  	return base64.RawURLEncoding.EncodeToString(d.Sum(nil))
    91  }
    92  
    93  type Sha256Digest [sha256.Size]byte
    94  
    95  // KeyDigest produces a Base64-encoded SHA256 digest of a
    96  // provided public key.
    97  func KeyDigest(key crypto.PublicKey) (Sha256Digest, error) {
    98  	switch t := key.(type) {
    99  	case *jose.JSONWebKey:
   100  		if t == nil {
   101  			return Sha256Digest{}, errors.New("cannot compute digest of nil key")
   102  		}
   103  		return KeyDigest(t.Key)
   104  	case jose.JSONWebKey:
   105  		return KeyDigest(t.Key)
   106  	default:
   107  		keyDER, err := x509.MarshalPKIXPublicKey(key)
   108  		if err != nil {
   109  			return Sha256Digest{}, err
   110  		}
   111  		return sha256.Sum256(keyDER), nil
   112  	}
   113  }
   114  
   115  // KeyDigestB64 produces a padded, standard Base64-encoded SHA256 digest of a
   116  // provided public key.
   117  func KeyDigestB64(key crypto.PublicKey) (string, error) {
   118  	digest, err := KeyDigest(key)
   119  	if err != nil {
   120  		return "", err
   121  	}
   122  	return base64.StdEncoding.EncodeToString(digest[:]), nil
   123  }
   124  
   125  // KeyDigestEquals determines whether two public keys have the same digest.
   126  func KeyDigestEquals(j, k crypto.PublicKey) bool {
   127  	digestJ, errJ := KeyDigestB64(j)
   128  	digestK, errK := KeyDigestB64(k)
   129  	// Keys that don't have a valid digest (due to marshalling problems)
   130  	// are never equal. So, e.g. nil keys are not equal.
   131  	if errJ != nil || errK != nil {
   132  		return false
   133  	}
   134  	return digestJ == digestK
   135  }
   136  
   137  // PublicKeysEqual determines whether two public keys are identical.
   138  func PublicKeysEqual(a, b crypto.PublicKey) (bool, error) {
   139  	switch ak := a.(type) {
   140  	case *rsa.PublicKey:
   141  		return ak.Equal(b), nil
   142  	case *ecdsa.PublicKey:
   143  		return ak.Equal(b), nil
   144  	default:
   145  		return false, fmt.Errorf("unsupported public key type %T", ak)
   146  	}
   147  }
   148  
   149  // SerialToString converts a certificate serial number (big.Int) to a String
   150  // consistently.
   151  func SerialToString(serial *big.Int) string {
   152  	return fmt.Sprintf("%036x", serial)
   153  }
   154  
   155  // StringToSerial converts a string into a certificate serial number (big.Int)
   156  // consistently.
   157  func StringToSerial(serial string) (*big.Int, error) {
   158  	var serialNum big.Int
   159  	if !ValidSerial(serial) {
   160  		return &serialNum, fmt.Errorf("invalid serial number %q", serial)
   161  	}
   162  	_, err := fmt.Sscanf(serial, "%036x", &serialNum)
   163  	return &serialNum, err
   164  }
   165  
   166  // ValidSerial tests whether the input string represents a syntactically
   167  // valid serial number, i.e., that it is a valid hex string between 32
   168  // and 36 characters long.
   169  func ValidSerial(serial string) bool {
   170  	// Originally, serial numbers were 32 hex characters long. We later increased
   171  	// them to 36, but we allow the shorter ones because they exist in some
   172  	// production databases.
   173  	if len(serial) != 32 && len(serial) != 36 {
   174  		return false
   175  	}
   176  	_, err := hex.DecodeString(serial)
   177  	return err == nil
   178  }
   179  
   180  // GetBuildID identifies what build is running.
   181  func GetBuildID() (retID string) {
   182  	retID = BuildID
   183  	if retID == "" {
   184  		retID = Unspecified
   185  	}
   186  	return
   187  }
   188  
   189  // GetBuildTime identifies when this build was made
   190  func GetBuildTime() (retID string) {
   191  	retID = BuildTime
   192  	if retID == "" {
   193  		retID = Unspecified
   194  	}
   195  	return
   196  }
   197  
   198  // GetBuildHost identifies the building host
   199  func GetBuildHost() (retID string) {
   200  	retID = BuildHost
   201  	if retID == "" {
   202  		retID = Unspecified
   203  	}
   204  	return
   205  }
   206  
   207  // IsAnyNilOrZero returns whether any of the supplied values are nil, or (if not)
   208  // if any of them is its type's zero-value. This is useful for validating that
   209  // all required fields on a proto message are present.
   210  func IsAnyNilOrZero(vals ...interface{}) bool {
   211  	for _, val := range vals {
   212  		switch v := val.(type) {
   213  		case nil:
   214  			return true
   215  		case []byte:
   216  			if len(v) == 0 {
   217  				return true
   218  			}
   219  		default:
   220  			if reflect.ValueOf(v).IsZero() {
   221  				return true
   222  			}
   223  		}
   224  	}
   225  	return false
   226  }
   227  
   228  // UniqueLowerNames returns the set of all unique names in the input after all
   229  // of them are lowercased. The returned names will be in their lowercased form
   230  // and sorted alphabetically.
   231  func UniqueLowerNames(names []string) (unique []string) {
   232  	nameMap := make(map[string]int, len(names))
   233  	for _, name := range names {
   234  		nameMap[strings.ToLower(name)] = 1
   235  	}
   236  
   237  	unique = make([]string, 0, len(nameMap))
   238  	for name := range nameMap {
   239  		unique = append(unique, name)
   240  	}
   241  	sort.Strings(unique)
   242  	return
   243  }
   244  
   245  // HashNames returns a hash of the names requested. This is intended for use
   246  // when interacting with the orderFqdnSets table and rate limiting.
   247  func HashNames(names []string) []byte {
   248  	names = UniqueLowerNames(names)
   249  	hash := sha256.Sum256([]byte(strings.Join(names, ",")))
   250  	return hash[:]
   251  }
   252  
   253  // LoadCert loads a PEM certificate specified by filename or returns an error
   254  func LoadCert(filename string) (*x509.Certificate, error) {
   255  	certPEM, err := os.ReadFile(filename)
   256  	if err != nil {
   257  		return nil, err
   258  	}
   259  	block, _ := pem.Decode(certPEM)
   260  	if block == nil {
   261  		return nil, fmt.Errorf("no data in cert PEM file %q", filename)
   262  	}
   263  	cert, err := x509.ParseCertificate(block.Bytes)
   264  	if err != nil {
   265  		return nil, err
   266  	}
   267  	return cert, nil
   268  }
   269  
   270  // retryJitter is used to prevent bunched retried queries from falling into lockstep
   271  const retryJitter = 0.2
   272  
   273  // RetryBackoff calculates a backoff time based on number of retries, will always
   274  // add jitter so requests that start in unison won't fall into lockstep. Because of
   275  // this the returned duration can always be larger than the maximum by a factor of
   276  // retryJitter. Adapted from
   277  // https://github.com/grpc/grpc-go/blob/v1.11.3/backoff.go#L77-L96
   278  func RetryBackoff(retries int, base, max time.Duration, factor float64) time.Duration {
   279  	if retries == 0 {
   280  		return 0
   281  	}
   282  	backoff, fMax := float64(base), float64(max)
   283  	for backoff < fMax && retries > 1 {
   284  		backoff *= factor
   285  		retries--
   286  	}
   287  	if backoff > fMax {
   288  		backoff = fMax
   289  	}
   290  	// Randomize backoff delays so that if a cluster of requests start at
   291  	// the same time, they won't operate in lockstep.
   292  	backoff *= (1 - retryJitter) + 2*retryJitter*mrand.Float64()
   293  	return time.Duration(backoff)
   294  }
   295  
   296  // IsASCII determines if every character in a string is encoded in
   297  // the ASCII character set.
   298  func IsASCII(str string) bool {
   299  	for _, r := range str {
   300  		if r > unicode.MaxASCII {
   301  			return false
   302  		}
   303  	}
   304  	return true
   305  }
   306  
   307  func Command() string {
   308  	return path.Base(os.Args[0])
   309  }
   310  

View as plain text