...

Source file src/golang.org/x/crypto/x509roots/nss/parser.go

Documentation: golang.org/x/crypto/x509roots/nss

     1  // Copyright 2023 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package nss provides functionality for parsing NSS certdata.txt
     6  // formatted certificate lists and extracting serverAuth roots. Most
     7  // users should not use this package themselves, and should instead
     8  // rely on the golang.org/x/crypto/x509roots/fallback package which
     9  // calls x509.SetFallbackRoots on a pre-parsed set of roots.
    10  package nss
    11  
    12  import (
    13  	"bufio"
    14  	"bytes"
    15  	"crypto/sha1"
    16  	"crypto/x509"
    17  	"errors"
    18  	"fmt"
    19  	"io"
    20  	"strconv"
    21  	"strings"
    22  	"time"
    23  )
    24  
    25  // Constraint is a constraint to be applied to a certificate or
    26  // certificate chain.
    27  type Constraint interface {
    28  	Kind() Kind
    29  }
    30  
    31  // Kind is the constraint kind, using the NSS enumeration.
    32  type Kind int
    33  
    34  const (
    35  	CKA_NSS_SERVER_DISTRUST_AFTER Kind = iota
    36  )
    37  
    38  // DistrustAfter is a Constraint that indicates a certificate has a
    39  // CKA_NSS_SERVER_DISTRUST_AFTER constraint. This constraint defines a date
    40  // after which any certificate issued which is rooted by the constrained
    41  // certificate should be distrusted.
    42  type DistrustAfter time.Time
    43  
    44  func (DistrustAfter) Kind() Kind {
    45  	return CKA_NSS_SERVER_DISTRUST_AFTER
    46  }
    47  
    48  // A Certificate represents a single trusted serverAuth certificate in the NSS
    49  // certdata.txt list and any constraints that should be applied to chains
    50  // rooted by it.
    51  type Certificate struct {
    52  	// Certificate is the parsed certificate
    53  	X509 *x509.Certificate
    54  	// Constraints contains a list of additional constraints that should be
    55  	// applied to any certificates that chain to Certificate. If there are
    56  	// any unknown constraints in the slice, Certificate should not be
    57  	// trusted.
    58  	Constraints []Constraint
    59  }
    60  
    61  func parseMulitLineOctal(s *bufio.Scanner) ([]byte, error) {
    62  	buf := bytes.NewBuffer(nil)
    63  	for s.Scan() {
    64  		if s.Text() == "END" {
    65  			break
    66  		}
    67  		b, err := strconv.Unquote(fmt.Sprintf("\"%s\"", s.Text()))
    68  		if err != nil {
    69  			return nil, err
    70  		}
    71  		buf.Write([]byte(b))
    72  	}
    73  	return buf.Bytes(), nil
    74  }
    75  
    76  type certObj struct {
    77  	c             *x509.Certificate
    78  	DistrustAfter *time.Time
    79  }
    80  
    81  func parseCertClass(s *bufio.Scanner) ([sha1.Size]byte, *certObj, error) {
    82  	var h [sha1.Size]byte
    83  	co := &certObj{}
    84  	for s.Scan() {
    85  		l := s.Text()
    86  		if l == "" {
    87  			// assume an empty newline indicates the end of a block
    88  			break
    89  		}
    90  		if strings.HasPrefix(l, "CKA_VALUE") {
    91  			b, err := parseMulitLineOctal(s)
    92  			if err != nil {
    93  				return h, nil, err
    94  			}
    95  			co.c, err = x509.ParseCertificate(b)
    96  			if err != nil {
    97  				return h, nil, err
    98  			}
    99  			h = sha1.Sum(b)
   100  		} else if strings.HasPrefix(l, "CKA_NSS_MOZILLA_CA_POLICY CK_BBOOL CK_FALSE") {
   101  			// we don't want it
   102  			return h, nil, nil
   103  		} else if l == "CKA_NSS_SERVER_DISTRUST_AFTER MULTILINE_OCTAL" {
   104  			dateStr, err := parseMulitLineOctal(s)
   105  			if err != nil {
   106  				return h, nil, err
   107  			}
   108  			t, err := time.Parse("060102150405Z0700", string(dateStr))
   109  			if err != nil {
   110  				return h, nil, err
   111  			}
   112  			co.DistrustAfter = &t
   113  		}
   114  	}
   115  	if co.c == nil {
   116  		return h, nil, errors.New("malformed CKO_CERTIFICATE object")
   117  	}
   118  	return h, co, nil
   119  }
   120  
   121  type trustObj struct {
   122  	trusted bool
   123  }
   124  
   125  func parseTrustClass(s *bufio.Scanner) ([sha1.Size]byte, *trustObj, error) {
   126  	var h [sha1.Size]byte
   127  	to := &trustObj{trusted: false} // default to untrusted
   128  
   129  	for s.Scan() {
   130  		l := s.Text()
   131  		if l == "" {
   132  			// assume an empty newline indicates the end of a block
   133  			break
   134  		}
   135  		if l == "CKA_CERT_SHA1_HASH MULTILINE_OCTAL" {
   136  			hash, err := parseMulitLineOctal(s)
   137  			if err != nil {
   138  				return h, nil, err
   139  			}
   140  			copy(h[:], hash)
   141  		} else if l == "CKA_TRUST_SERVER_AUTH CK_TRUST CKT_NSS_TRUSTED_DELEGATOR" {
   142  			// we only care about server auth
   143  			to.trusted = true
   144  		}
   145  	}
   146  
   147  	return h, to, nil
   148  }
   149  
   150  // Parse parses a NSS certdata.txt formatted file, returning only
   151  // trusted serverAuth roots, as well as any additional constraints. This parser
   152  // is very opinionated, only returning roots that are currently trusted for
   153  // serverAuth. As such roots returned by this package should only be used for
   154  // making trust decisions about serverAuth certificates, as the trust status for
   155  // other uses is not considered. Using the roots returned by this package for
   156  // trust decisions should be done carefully.
   157  //
   158  // Some roots returned by the parser may include additional constraints
   159  // (currently only DistrustAfter) which need to be considered when verifying
   160  // certificates which chain to them.
   161  //
   162  // Parse is not intended to be a general purpose parser for certdata.txt.
   163  func Parse(r io.Reader) ([]*Certificate, error) {
   164  	// certdata.txt is a rather strange format. It is essentially a list of
   165  	// textual PKCS#11 objects, delimited by empty lines. There are two main
   166  	// types of objects, certificates (CKO_CERTIFICATE) and trust definitions
   167  	// (CKO_NSS_TRUST). These objects appear to alternate, but this ordering is
   168  	// not defined anywhere, and should probably not be relied on. A single root
   169  	// certificate requires both the certificate object and the trust definition
   170  	// object in order to be properly understood.
   171  	//
   172  	// The list contains not just serverAuth certificates, so we need to be
   173  	// careful to only extract certificates which have the serverAuth trust bit
   174  	// set. Similarly there are a number of trust related bool fields that
   175  	// appear to _always_ be CKA_TRUE, but it seems unsafe to assume this is the
   176  	// case, so we should always double check.
   177  	//
   178  	// Since we only really care about a couple of fields, this parser throws
   179  	// away a lot of information, essentially just consuming CKA_CLASS objects
   180  	// and looking for the individual fields we care about. We could write a
   181  	// siginificantly more complex parser, which handles the entire format, but
   182  	// it feels like that would be over engineered for the little information
   183  	// that we really care about.
   184  
   185  	scanner := bufio.NewScanner(r)
   186  
   187  	type nssEntry struct {
   188  		cert  *certObj
   189  		trust *trustObj
   190  	}
   191  	entries := map[[sha1.Size]byte]*nssEntry{}
   192  
   193  	for scanner.Scan() {
   194  		// scan until we hit CKA_CLASS
   195  		if !strings.HasPrefix(scanner.Text(), "CKA_CLASS") {
   196  			continue
   197  		}
   198  
   199  		f := strings.Fields(scanner.Text())
   200  		if len(f) != 3 {
   201  			return nil, errors.New("malformed CKA_CLASS")
   202  		}
   203  		switch f[2] {
   204  		case "CKO_CERTIFICATE":
   205  			h, co, err := parseCertClass(scanner)
   206  			if err != nil {
   207  				return nil, err
   208  			}
   209  			if co != nil {
   210  				e, ok := entries[h]
   211  				if !ok {
   212  					e = &nssEntry{}
   213  					entries[h] = e
   214  				}
   215  				e.cert = co
   216  			}
   217  
   218  		case "CKO_NSS_TRUST":
   219  			h, to, err := parseTrustClass(scanner)
   220  			if err != nil {
   221  				return nil, err
   222  			}
   223  			if to != nil {
   224  				e, ok := entries[h]
   225  				if !ok {
   226  					e = &nssEntry{}
   227  					entries[h] = e
   228  				}
   229  				e.trust = to
   230  			}
   231  		}
   232  	}
   233  	if err := scanner.Err(); err != nil {
   234  		return nil, err
   235  	}
   236  
   237  	var certs []*Certificate
   238  	for h, e := range entries {
   239  		if e.cert == nil && e.trust != nil {
   240  			// We may skip some certificates which are distrusted due to mozilla
   241  			// policy (CKA_NSS_MOZILLA_CA_POLICY CK_BBOOL CK_FALSE), which means
   242  			// we might get entries that appear to have a trust object, but no
   243  			// certificate. We can just continue on here.
   244  			continue
   245  		} else if e.cert != nil && e.trust == nil {
   246  			return nil, fmt.Errorf("missing trust object for certificate with SHA1 hash: %x", h)
   247  		}
   248  		if !e.trust.trusted {
   249  			continue
   250  		}
   251  		nssCert := &Certificate{X509: e.cert.c}
   252  		if e.cert.DistrustAfter != nil {
   253  			nssCert.Constraints = append(nssCert.Constraints, DistrustAfter(*e.cert.DistrustAfter))
   254  		}
   255  		certs = append(certs, nssCert)
   256  	}
   257  
   258  	return certs, nil
   259  }
   260  

View as plain text