ubiquity_platform.go 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. package ubiquity
  2. // This is for cross-platform ubiquity. Basically, here we deal with issues about whether a cert chain
  3. // is acceptable for different platforms, including desktop and mobile ones., and about how to compare
  4. // two chains under the context of cross-platform ubiquity.
  5. import (
  6. "crypto/sha1"
  7. "crypto/x509"
  8. "encoding/json"
  9. "fmt"
  10. "io/ioutil"
  11. "path"
  12. "path/filepath"
  13. "github.com/cloudflare/cfssl/helpers"
  14. "github.com/cloudflare/cfssl/log"
  15. )
  16. // SHA1RawPublicKey returns a SHA1 hash of the raw certificate public key
  17. func SHA1RawPublicKey(cert *x509.Certificate) string {
  18. return fmt.Sprintf("%x", sha1.Sum(cert.RawSubjectPublicKeyInfo))
  19. }
  20. // CertSet is a succint set of x509 certificates which only stores certificates' SHA1 hashes.
  21. type CertSet map[string]bool
  22. // Lookup returns whether a certificate is stored in the set.
  23. func (s CertSet) Lookup(cert *x509.Certificate) bool {
  24. return s[SHA1RawPublicKey(cert)]
  25. }
  26. // Add adds a certificate to the set.
  27. func (s CertSet) Add(cert *x509.Certificate) {
  28. s[SHA1RawPublicKey(cert)] = true
  29. }
  30. // A Platform contains ubiquity information on supported crypto algorithms and root certificate store name.
  31. type Platform struct {
  32. Name string `json:"name"`
  33. Weight int `json:"weight"`
  34. HashAlgo string `json:"hash_algo"`
  35. KeyAlgo string `json:"key_algo"`
  36. KeyStoreFile string `json:"keystore"`
  37. KeyStore CertSet
  38. HashUbiquity HashUbiquity
  39. KeyAlgoUbiquity KeyAlgoUbiquity
  40. }
  41. // Trust returns whether the platform has the root cert in the trusted store.
  42. func (p Platform) Trust(root *x509.Certificate) bool {
  43. // the key store is empty iff the platform doesn't carry a root store and trust whatever root store
  44. // is supplied. An example is Chrome. Such platforms should not show up in the untrusted platform
  45. // list. So always return true here. Also this won't hurt ubiquity scoring because such platforms give
  46. // no differentiation on root cert selection.
  47. if len(p.KeyStore) == 0 {
  48. return true
  49. }
  50. return p.KeyStore.Lookup(root)
  51. }
  52. func (p Platform) hashUbiquity() HashUbiquity {
  53. switch p.HashAlgo {
  54. case "SHA1":
  55. return SHA1Ubiquity
  56. case "SHA2":
  57. return SHA2Ubiquity
  58. default:
  59. return UnknownHashUbiquity
  60. }
  61. }
  62. func (p Platform) keyAlgoUbiquity() KeyAlgoUbiquity {
  63. switch p.KeyAlgo {
  64. case "RSA":
  65. return RSAUbiquity
  66. case "ECDSA256":
  67. return ECDSA256Ubiquity
  68. case "ECDSA384":
  69. return ECDSA384Ubiquity
  70. case "ECDSA521":
  71. return ECDSA521Ubiquity
  72. default:
  73. return UnknownAlgoUbiquity
  74. }
  75. }
  76. // ParseAndLoad converts HashAlgo and KeyAlgo to corresponding ubiquity value and load
  77. // certificates into internal KeyStore from KeyStoreFiles
  78. func (p *Platform) ParseAndLoad() (ok bool) {
  79. p.HashUbiquity = p.hashUbiquity()
  80. p.KeyAlgoUbiquity = p.keyAlgoUbiquity()
  81. p.KeyStore = map[string]bool{}
  82. if p.KeyStoreFile != "" {
  83. pemBytes, err := ioutil.ReadFile(p.KeyStoreFile)
  84. if err != nil {
  85. log.Error(err)
  86. return false
  87. }
  88. // Best effort parsing the PEMs such that ignore all borken pem,
  89. // since some of CA certs have negative serial number which trigger errors.
  90. for len(pemBytes) > 0 {
  91. var certs []*x509.Certificate
  92. certs, rest, err := helpers.ParseOneCertificateFromPEM(pemBytes)
  93. // If one certificate object is parsed, possibly a PKCS#7
  94. // structure containing multiple certs, record the raw SHA1 hash(es).
  95. if err == nil && certs != nil {
  96. for _, cert := range certs {
  97. p.KeyStore.Add(cert)
  98. }
  99. }
  100. if len(rest) < len(pemBytes) {
  101. pemBytes = rest
  102. } else {
  103. // No progress in bytes parsing, bail out.
  104. break
  105. }
  106. }
  107. }
  108. if p.HashUbiquity <= UnknownHashUbiquity ||
  109. p.KeyAlgoUbiquity <= UnknownAlgoUbiquity {
  110. return false
  111. }
  112. return true
  113. }
  114. // Platforms is the list of platforms against which ubiquity bundling will be optimized.
  115. var Platforms []Platform
  116. // LoadPlatforms reads the file content as a json object array and convert it
  117. // to Platforms.
  118. func LoadPlatforms(filename string) error {
  119. // if filename is empty, skip the metadata loading
  120. if filename == "" {
  121. return nil
  122. }
  123. relativePath := filepath.Dir(filename)
  124. // Attempt to load root certificate metadata
  125. log.Debug("Loading platform metadata: ", filename)
  126. bytes, err := ioutil.ReadFile(filename)
  127. if err != nil {
  128. return fmt.Errorf("platform metadata failed to load: %v", err)
  129. }
  130. var rawPlatforms []Platform
  131. if bytes != nil {
  132. err = json.Unmarshal(bytes, &rawPlatforms)
  133. if err != nil {
  134. return fmt.Errorf("platform metadata failed to parse: %v", err)
  135. }
  136. }
  137. for _, platform := range rawPlatforms {
  138. if platform.KeyStoreFile != "" {
  139. platform.KeyStoreFile = path.Join(relativePath, platform.KeyStoreFile)
  140. }
  141. ok := platform.ParseAndLoad()
  142. if !ok {
  143. // erase all loaded platforms
  144. Platforms = nil
  145. return fmt.Errorf("fail to finalize the parsing of platform metadata: %v", platform)
  146. }
  147. log.Infof("Platform metadata is loaded: %v %v", platform.Name, len(platform.KeyStore))
  148. Platforms = append(Platforms, platform)
  149. }
  150. return nil
  151. }
  152. // UntrustedPlatforms returns a list of platforms which don't trust the root certificate.
  153. func UntrustedPlatforms(root *x509.Certificate) []string {
  154. ret := []string{}
  155. for _, platform := range Platforms {
  156. if !platform.Trust(root) {
  157. ret = append(ret, platform.Name)
  158. }
  159. }
  160. return ret
  161. }
  162. // CrossPlatformUbiquity returns a ubiquity score (presumably relecting the market share in percentage)
  163. // based on whether the given chain can be verified with the different platforms' root certificate stores.
  164. func CrossPlatformUbiquity(chain []*x509.Certificate) int {
  165. // There is no root store info, every chain is equal weighted as 0.
  166. if len(Platforms) == 0 {
  167. return 0
  168. }
  169. totalWeight := 0
  170. // A chain is viable with the platform if
  171. // 1. the root is in the platform's root store
  172. // 2. the chain satisfy the minimal constraints on hash function and key algorithm.
  173. root := chain[len(chain)-1]
  174. for _, platform := range Platforms {
  175. if platform.Trust(root) {
  176. switch {
  177. case platform.HashUbiquity <= ChainHashUbiquity(chain) && platform.KeyAlgoUbiquity <= ChainKeyAlgoUbiquity(chain):
  178. totalWeight += platform.Weight
  179. }
  180. }
  181. }
  182. return totalWeight
  183. }
  184. // ComparePlatformUbiquity compares the cross-platform ubiquity between chain1 and chain2.
  185. func ComparePlatformUbiquity(chain1, chain2 []*x509.Certificate) int {
  186. w1 := CrossPlatformUbiquity(chain1)
  187. w2 := CrossPlatformUbiquity(chain2)
  188. return w1 - w2
  189. }
  190. // SHA2Homogeneity returns 1 if the chain contains only SHA-2 certs (excluding root). Otherwise it returns 0.
  191. func SHA2Homogeneity(chain []*x509.Certificate) int {
  192. for i := 0; i < len(chain)-1; i++ {
  193. if hashUbiquity(chain[i]) != SHA2Ubiquity {
  194. return 0
  195. }
  196. }
  197. return 1
  198. }
  199. // CompareSHA2Homogeneity compares the chains based on SHA2 homogeneity. Full SHA-2 chain (excluding root) is rated higher that the rest.
  200. func CompareSHA2Homogeneity(chain1, chain2 []*x509.Certificate) int {
  201. w1 := SHA2Homogeneity(chain1)
  202. w2 := SHA2Homogeneity(chain2)
  203. return w1 - w2
  204. }