kubernetes/vendor/github.com/cloudflare/cfssl/ubiquity/ubiquity_platform.go
2018-08-08 21:22:01 -07:00

231 lines
6.8 KiB
Go

package ubiquity
// This is for cross-platform ubiquity. Basically, here we deal with issues about whether a cert chain
// is acceptable for different platforms, including desktop and mobile ones., and about how to compare
// two chains under the context of cross-platform ubiquity.
import (
"crypto/sha1"
"crypto/x509"
"encoding/json"
"fmt"
"io/ioutil"
"path"
"path/filepath"
"github.com/cloudflare/cfssl/helpers"
"github.com/cloudflare/cfssl/log"
)
// SHA1RawPublicKey returns a SHA1 hash of the raw certificate public key
func SHA1RawPublicKey(cert *x509.Certificate) string {
return fmt.Sprintf("%x", sha1.Sum(cert.RawSubjectPublicKeyInfo))
}
// CertSet is a succint set of x509 certificates which only stores certificates' SHA1 hashes.
type CertSet map[string]bool
// Lookup returns whether a certificate is stored in the set.
func (s CertSet) Lookup(cert *x509.Certificate) bool {
return s[SHA1RawPublicKey(cert)]
}
// Add adds a certificate to the set.
func (s CertSet) Add(cert *x509.Certificate) {
s[SHA1RawPublicKey(cert)] = true
}
// A Platform contains ubiquity information on supported crypto algorithms and root certificate store name.
type Platform struct {
Name string `json:"name"`
Weight int `json:"weight"`
HashAlgo string `json:"hash_algo"`
KeyAlgo string `json:"key_algo"`
KeyStoreFile string `json:"keystore"`
KeyStore CertSet
HashUbiquity HashUbiquity
KeyAlgoUbiquity KeyAlgoUbiquity
}
// Trust returns whether the platform has the root cert in the trusted store.
func (p Platform) Trust(root *x509.Certificate) bool {
// the key store is empty iff the platform doesn't carry a root store and trust whatever root store
// is supplied. An example is Chrome. Such platforms should not show up in the untrusted platform
// list. So always return true here. Also this won't hurt ubiquity scoring because such platforms give
// no differentiation on root cert selection.
if len(p.KeyStore) == 0 {
return true
}
return p.KeyStore.Lookup(root)
}
func (p Platform) hashUbiquity() HashUbiquity {
switch p.HashAlgo {
case "SHA1":
return SHA1Ubiquity
case "SHA2":
return SHA2Ubiquity
default:
return UnknownHashUbiquity
}
}
func (p Platform) keyAlgoUbiquity() KeyAlgoUbiquity {
switch p.KeyAlgo {
case "RSA":
return RSAUbiquity
case "ECDSA256":
return ECDSA256Ubiquity
case "ECDSA384":
return ECDSA384Ubiquity
case "ECDSA521":
return ECDSA521Ubiquity
default:
return UnknownAlgoUbiquity
}
}
// ParseAndLoad converts HashAlgo and KeyAlgo to corresponding ubiquity value and load
// certificates into internal KeyStore from KeyStoreFiles
func (p *Platform) ParseAndLoad() (ok bool) {
p.HashUbiquity = p.hashUbiquity()
p.KeyAlgoUbiquity = p.keyAlgoUbiquity()
p.KeyStore = map[string]bool{}
if p.KeyStoreFile != "" {
pemBytes, err := ioutil.ReadFile(p.KeyStoreFile)
if err != nil {
log.Error(err)
return false
}
// Best effort parsing the PEMs such that ignore all borken pem,
// since some of CA certs have negative serial number which trigger errors.
for len(pemBytes) > 0 {
var certs []*x509.Certificate
certs, rest, err := helpers.ParseOneCertificateFromPEM(pemBytes)
// If one certificate object is parsed, possibly a PKCS#7
// structure containing multiple certs, record the raw SHA1 hash(es).
if err == nil && certs != nil {
for _, cert := range certs {
p.KeyStore.Add(cert)
}
}
if len(rest) < len(pemBytes) {
pemBytes = rest
} else {
// No progress in bytes parsing, bail out.
break
}
}
}
if p.HashUbiquity <= UnknownHashUbiquity ||
p.KeyAlgoUbiquity <= UnknownAlgoUbiquity {
return false
}
return true
}
// Platforms is the list of platforms against which ubiquity bundling will be optimized.
var Platforms []Platform
// LoadPlatforms reads the file content as a json object array and convert it
// to Platforms.
func LoadPlatforms(filename string) error {
// if filename is empty, skip the metadata loading
if filename == "" {
return nil
}
relativePath := filepath.Dir(filename)
// Attempt to load root certificate metadata
log.Debug("Loading platform metadata: ", filename)
bytes, err := ioutil.ReadFile(filename)
if err != nil {
return fmt.Errorf("platform metadata failed to load: %v", err)
}
var rawPlatforms []Platform
if bytes != nil {
err = json.Unmarshal(bytes, &rawPlatforms)
if err != nil {
return fmt.Errorf("platform metadata failed to parse: %v", err)
}
}
for _, platform := range rawPlatforms {
if platform.KeyStoreFile != "" {
platform.KeyStoreFile = path.Join(relativePath, platform.KeyStoreFile)
}
ok := platform.ParseAndLoad()
if !ok {
// erase all loaded platforms
Platforms = nil
return fmt.Errorf("fail to finalize the parsing of platform metadata: %v", platform)
}
log.Infof("Platform metadata is loaded: %v %v", platform.Name, len(platform.KeyStore))
Platforms = append(Platforms, platform)
}
return nil
}
// UntrustedPlatforms returns a list of platforms which don't trust the root certificate.
func UntrustedPlatforms(root *x509.Certificate) []string {
ret := []string{}
for _, platform := range Platforms {
if !platform.Trust(root) {
ret = append(ret, platform.Name)
}
}
return ret
}
// CrossPlatformUbiquity returns a ubiquity score (presumably relecting the market share in percentage)
// based on whether the given chain can be verified with the different platforms' root certificate stores.
func CrossPlatformUbiquity(chain []*x509.Certificate) int {
// There is no root store info, every chain is equal weighted as 0.
if len(Platforms) == 0 {
return 0
}
totalWeight := 0
// A chain is viable with the platform if
// 1. the root is in the platform's root store
// 2. the chain satisfy the minimal constraints on hash function and key algorithm.
root := chain[len(chain)-1]
for _, platform := range Platforms {
if platform.Trust(root) {
switch {
case platform.HashUbiquity <= ChainHashUbiquity(chain) && platform.KeyAlgoUbiquity <= ChainKeyAlgoUbiquity(chain):
totalWeight += platform.Weight
}
}
}
return totalWeight
}
// ComparePlatformUbiquity compares the cross-platform ubiquity between chain1 and chain2.
func ComparePlatformUbiquity(chain1, chain2 []*x509.Certificate) int {
w1 := CrossPlatformUbiquity(chain1)
w2 := CrossPlatformUbiquity(chain2)
return w1 - w2
}
// SHA2Homogeneity returns 1 if the chain contains only SHA-2 certs (excluding root). Otherwise it returns 0.
func SHA2Homogeneity(chain []*x509.Certificate) int {
for i := 0; i < len(chain)-1; i++ {
if hashUbiquity(chain[i]) != SHA2Ubiquity {
return 0
}
}
return 1
}
// CompareSHA2Homogeneity compares the chains based on SHA2 homogeneity. Full SHA-2 chain (excluding root) is rated higher that the rest.
func CompareSHA2Homogeneity(chain1, chain2 []*x509.Certificate) int {
w1 := SHA2Homogeneity(chain1)
w2 := SHA2Homogeneity(chain2)
return w1 - w2
}