123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231 |
- 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
- }
|