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  }