/
ubiquity_platform.go
230 lines (204 loc) · 6.82 KB
/
ubiquity_platform.go
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"
"os"
"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 := os.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 := os.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
}