231 lines
6.8 KiB
Go
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
|
|
}
|