1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
|
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 (persumably 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
}
|