github.com/zmap/zcrypto@v0.0.0-20240512203510-0fef58d9a9db/x509/revocation/mozilla/mozilla.go (about) 1 package mozilla 2 3 import ( 4 "bytes" 5 "crypto/sha256" 6 "encoding/base64" 7 "encoding/json" 8 "errors" 9 "fmt" 10 "io/ioutil" 11 "math/big" 12 "net/http" 13 "time" 14 15 "github.com/zmap/zcrypto/encoding/asn1" 16 "github.com/zmap/zcrypto/x509" 17 "github.com/zmap/zcrypto/x509/pkix" 18 ) 19 20 // Provider specifies OneCRL provider interface 21 type Provider interface { 22 FetchAndParse() (*OneCRL, error) 23 } 24 25 const ( 26 // KintoRequestURL specifies a pre-populated URL where to send request 27 KintoRequestURL = "https://settings.prod.mozaws.net/v1/buckets/security-state-staging/collections/onecrl/records" 28 // OneCRLDistPoint specifies a pre-populated URL where to send request 29 OneCRLDistPoint = "https://firefox.settings.services.mozilla.com/v1/buckets/blocklists/collections/certificates/records" 30 ) 31 32 // defaultProvider provides default Provider 33 type defaultProvider struct { 34 requestURL string 35 } 36 37 // NewProvider returns default Provider 38 func NewProvider(requestURL string) Provider { 39 return &defaultProvider{ 40 requestURL: requestURL, 41 } 42 } 43 44 // FetchAndParse - fetch from distribution point, parse to OneCRL struct as defined above 45 func FetchAndParse() (*OneCRL, error) { 46 return NewProvider(OneCRLDistPoint).FetchAndParse() 47 } 48 49 // OneCRL - data structure for storing OneCRL data, used by methods below 50 type OneCRL struct { 51 IssuerLists map[string]*IssuerList 52 53 // Blocked provides a list of revoked entries by Subject and PubKeyHash 54 Blocked []*SubjectAndPublicKey 55 } 56 57 // IssuerList - list of Entry for a given issuer 58 type IssuerList struct { 59 Issuer *pkix.Name 60 Entries []*Entry 61 } 62 63 // Entry - entry for a single certificate 64 type Entry struct { 65 ID string 66 Schema time.Time 67 Details EntryDetails 68 Enabled bool 69 Issuer *pkix.Name 70 SerialNumber *big.Int 71 SubjectAndPublicKey *SubjectAndPublicKey 72 LastModified time.Time 73 } 74 75 // SubjectAndPublicKey specifies a revocation entry by Subject and PubKeyHash 76 type SubjectAndPublicKey struct { 77 RawSubject []byte 78 Subject *pkix.Name 79 PubKeyHash []byte 80 } 81 82 // EntryDetails - revocation details for a single entry 83 type EntryDetails struct { 84 Bug string `json:"bug,omitempty"` 85 Who string `json:"who,omitempty"` 86 Why string `json:"why,omitempty"` 87 Name string `json:"name,omitempty"` 88 Created *time.Time `json:"created,omitempty"` 89 } 90 91 type record struct { 92 ID string `json:"id,omitempty"` 93 IssuerName string `json:"issuerName,omitempty"` 94 SerialNumber string `json:"serialNumber,omitempty"` 95 Subject string `json:"subject,omitempty"` 96 PubKeyHash string `json:"pubKeyHash,omitempty"` 97 Enabled bool `json:"enabled"` 98 Schema int `json:"schema"` 99 LastModified int `json:"last_modified"` 100 Details struct { 101 Who string `json:"who"` 102 Created string `json:"created"` 103 Bug string `json:"bug"` 104 Name string `json:"name"` 105 Why string `json:"why"` 106 } `json:"details"` 107 } 108 109 func decodePkixName(name string) (*pkix.Name, []byte, error) { 110 issuerBytes, err := base64.StdEncoding.DecodeString(name) 111 if err != nil { 112 return nil, nil, err 113 } 114 var issuerRDN pkix.RDNSequence 115 _, err = asn1.Unmarshal(issuerBytes, &issuerRDN) 116 if err != nil { 117 return nil, nil, err 118 } 119 iss := new(pkix.Name) 120 iss.FillFromRDNSequence(&issuerRDN) 121 return iss, issuerBytes, nil 122 } 123 124 // UnmarshalJSON implements the json.Unmarshaler interface 125 func (entry *Entry) UnmarshalJSON(b []byte) error { 126 aux := &record{} 127 if err := json.Unmarshal(b, &aux); err != nil { 128 return err 129 } 130 schemaSeconds := int64(aux.Schema) / 1000 131 schema := time.Unix(schemaSeconds, 0) 132 lastModifiedSeconds := int64(aux.LastModified) / 1000 133 lastModified := time.Unix(lastModifiedSeconds, 0) 134 135 var createdAt *time.Time 136 if aux.Details.Created != "" { 137 var t time.Time 138 if err := t.UnmarshalJSON([]byte(aux.Details.Created)); err == nil { 139 createdAt = &t 140 } 141 } 142 143 var err error 144 var subjectAndPublicKey *SubjectAndPublicKey 145 var issuer *pkix.Name 146 var serialNumber *big.Int 147 148 if aux.Subject != "" && aux.PubKeyHash != "" { 149 subj, rawSubj, err := decodePkixName(aux.Subject) 150 if err != nil { 151 return fmt.Errorf("failed to unbase64 Subject: %v", err) 152 } 153 rawPubKey, err := base64.StdEncoding.DecodeString(aux.PubKeyHash) 154 if err != nil { 155 return fmt.Errorf("failed to unbase64 Subject: %v", err) 156 } 157 158 subjectAndPublicKey = &SubjectAndPublicKey{ 159 Subject: subj, 160 RawSubject: rawSubj, 161 PubKeyHash: rawPubKey, 162 } 163 } else { 164 serialNumberBytes, _ := base64.StdEncoding.DecodeString(aux.SerialNumber) 165 serialNumber = new(big.Int).SetBytes(serialNumberBytes) 166 issuer, _, err = decodePkixName(aux.IssuerName) 167 if err != nil { 168 return fmt.Errorf("failed to unbase64 IssuerName: %v", err) 169 } 170 } 171 172 *entry = Entry{ 173 ID: aux.ID, 174 Schema: schema, 175 Details: EntryDetails{ 176 Created: createdAt, 177 Who: aux.Details.Who, 178 Bug: aux.Details.Bug, 179 Name: aux.Details.Name, 180 Why: aux.Details.Why, 181 }, 182 Enabled: aux.Enabled, 183 Issuer: issuer, 184 SerialNumber: serialNumber, 185 SubjectAndPublicKey: subjectAndPublicKey, 186 LastModified: lastModified, 187 } 188 return nil 189 } 190 191 // FindIssuer - given an issuer pkix.name, find its corresponding IssuerList 192 func (c *OneCRL) FindIssuer(issuer *pkix.Name) *IssuerList { 193 issuerStr := issuer.String() 194 return c.IssuerLists[issuerStr] 195 } 196 197 // FetchAndParse - fetch from distribution point, parse to OneCRL struct as defined above 198 func (p *defaultProvider) FetchAndParse() (*OneCRL, error) { 199 raw, err := fetch(p.requestURL) 200 if err != nil { 201 return nil, err 202 } 203 return Parse(raw) 204 } 205 206 func fetch(url string) ([]byte, error) { 207 resp, err := http.Get(url) 208 if err != nil { 209 return nil, fmt.Errorf("Failed to get current OneCRL: %v", err) 210 } 211 212 bodyBytes, err := ioutil.ReadAll(resp.Body) 213 resp.Body.Close() 214 if err != nil { 215 return nil, fmt.Errorf("Failed to read OneCRL response: %v", err) 216 } 217 return bodyBytes, nil 218 } 219 220 // Parse - given raw bytes of OneCRL, parse and create OneCRL Object 221 func Parse(raw []byte) (*OneCRL, error) { 222 rawOneCRL := struct { 223 Data []Entry `json:"data"` 224 }{} 225 if err := json.Unmarshal(raw, &rawOneCRL); err != nil { 226 return nil, errors.New("Could not parse OneCRL: " + err.Error()) 227 } 228 oneCRL := &OneCRL{ 229 IssuerLists: make(map[string]*IssuerList), 230 Blocked: make([]*SubjectAndPublicKey, 0), 231 } 232 for i := range rawOneCRL.Data { 233 entry := &(rawOneCRL.Data[i]) 234 235 if entry.SubjectAndPublicKey != nil { 236 oneCRL.Blocked = append(oneCRL.Blocked, entry.SubjectAndPublicKey) 237 continue 238 } 239 240 issuerList := oneCRL.FindIssuer(entry.Issuer) 241 if issuerList != nil { // if list already exists for this issuer, append 242 issuerList.Entries = append(issuerList.Entries, entry) 243 } else { // create new list for this issuer 244 newList := &IssuerList{ 245 Issuer: entry.Issuer, 246 } 247 newList.Entries = append(newList.Entries, entry) 248 oneCRL.IssuerLists[entry.Issuer.String()] = newList 249 } 250 } 251 return oneCRL, nil 252 } 253 254 // Check - Given a parsed OneCRL, check if a given cert is present 255 func (c *OneCRL) Check(cert *x509.Certificate) *Entry { 256 // check for BlockedSPKIs first 257 for _, blocked := range c.Blocked { 258 if bytes.Equal(blocked.RawSubject, cert.RawSubject) { 259 pubKeyData, _ := x509.MarshalPKIXPublicKey(cert.PublicKey) 260 hash := sha256.Sum256(pubKeyData) 261 if bytes.Equal(blocked.PubKeyHash, hash[:]) { 262 return &Entry{ 263 SubjectAndPublicKey: &SubjectAndPublicKey{ 264 RawSubject: cert.RawSubject, 265 Subject: &cert.Subject, 266 PubKeyHash: hash[:], 267 }, 268 } 269 } 270 } 271 } 272 273 issuersRevokedCerts := c.FindIssuer(&cert.Issuer) 274 if issuersRevokedCerts == nil { // no entries for this issuer 275 return nil 276 } 277 for _, entry := range issuersRevokedCerts.Entries { 278 if entry.SerialNumber.Cmp(cert.SerialNumber) == 0 { 279 return entry 280 } // cert not found if for loop completes 281 } 282 283 return nil 284 }