...

Source file src/github.com/letsencrypt/boulder/linter/lints/rfc/lint_crl_has_valid_timestamps.go

Documentation: github.com/letsencrypt/boulder/linter/lints/rfc

     1  package rfc
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"time"
     7  
     8  	"github.com/zmap/zcrypto/x509"
     9  	"github.com/zmap/zlint/v3/lint"
    10  	"github.com/zmap/zlint/v3/util"
    11  	"golang.org/x/crypto/cryptobyte"
    12  	cryptobyte_asn1 "golang.org/x/crypto/cryptobyte/asn1"
    13  )
    14  
    15  const (
    16  	utcTimeFormat         = "YYMMDDHHMMSSZ"
    17  	generalizedTimeFormat = "YYYYMMDDHHMMSSZ"
    18  )
    19  
    20  type crlHasValidTimestamps struct{}
    21  
    22  /************************************************
    23  RFC 5280: 5.1.2.4
    24  CRL issuers conforming to this profile MUST encode thisUpdate as UTCTime for
    25  dates through the year 2049. CRL issuers conforming to this profile MUST encode
    26  thisUpdate as GeneralizedTime for dates in the year 2050 or later. Conforming
    27  applications MUST be able to process dates that are encoded in either UTCTime or
    28  GeneralizedTime.
    29  
    30  Where encoded as UTCTime, thisUpdate MUST be specified and interpreted as
    31  defined in Section 4.1.2.5.1. Where encoded as GeneralizedTime, thisUpdate MUST
    32  be specified and interpreted as defined in Section 4.1.2.5.2.
    33  
    34  RFC 5280: 5.1.2.5
    35  CRL issuers conforming to this profile MUST encode nextUpdate as UTCTime for
    36  dates through the year 2049. CRL issuers conforming to this profile MUST encode
    37  nextUpdate as GeneralizedTime for dates in the year 2050 or later. Conforming
    38  applications MUST be able to process dates that are encoded in either UTCTime or
    39  GeneralizedTime.
    40  
    41  Where encoded as UTCTime, nextUpdate MUST be specified and interpreted as
    42  defined in Section 4.1.2.5.1. Where encoded as GeneralizedTime, nextUpdate MUST
    43  be specified and interpreted as defined in Section 4.1.2.5.2.
    44  
    45  RFC 5280: 5.1.2.6
    46  The time for revocationDate MUST be expressed as described in Section 5.1.2.4.
    47  
    48  RFC 5280: 4.1.2.5.1
    49  UTCTime values MUST be expressed in Greenwich Mean Time (Zulu) and MUST include
    50  seconds (i.e., times are YYMMDDHHMMSSZ), even where the number of seconds is
    51  zero.
    52  
    53  RFC 5280: 4.1.2.5.2
    54  GeneralizedTime values MUST be expressed in Greenwich Mean Time (Zulu) and MUST
    55  include seconds (i.e., times are YYYYMMDDHHMMSSZ), even where the number of
    56  seconds is zero. GeneralizedTime values MUST NOT include fractional seconds.
    57  ************************************************/
    58  
    59  func init() {
    60  	lint.RegisterRevocationListLint(&lint.RevocationListLint{
    61  		LintMetadata: lint.LintMetadata{
    62  			Name:          "e_crl_has_valid_timestamps",
    63  			Description:   "CRL thisUpdate, nextUpdate, and revocationDates must be properly encoded",
    64  			Citation:      "RFC 5280: 5.1.2.4, 5.1.2.5, and 5.1.2.6",
    65  			Source:        lint.RFC5280,
    66  			EffectiveDate: util.RFC5280Date,
    67  		},
    68  		Lint: NewCrlHasValidTimestamps,
    69  	})
    70  }
    71  
    72  func NewCrlHasValidTimestamps() lint.RevocationListLintInterface {
    73  	return &crlHasValidTimestamps{}
    74  }
    75  
    76  func (l *crlHasValidTimestamps) CheckApplies(c *x509.RevocationList) bool {
    77  	return true
    78  }
    79  
    80  func (l *crlHasValidTimestamps) Execute(c *x509.RevocationList) *lint.LintResult {
    81  	input := cryptobyte.String(c.RawTBSRevocationList)
    82  	lintFail := lint.LintResult{
    83  		Status:  lint.Error,
    84  		Details: "Failed to re-parse tbsCertList during linting",
    85  	}
    86  
    87  	// Read tbsCertList.
    88  	var tbs cryptobyte.String
    89  	if !input.ReadASN1(&tbs, cryptobyte_asn1.SEQUENCE) {
    90  		return &lintFail
    91  	}
    92  
    93  	// Skip (optional) version.
    94  	if !tbs.SkipOptionalASN1(cryptobyte_asn1.INTEGER) {
    95  		return &lintFail
    96  	}
    97  
    98  	// Skip signature.
    99  	if !tbs.SkipASN1(cryptobyte_asn1.SEQUENCE) {
   100  		return &lintFail
   101  	}
   102  
   103  	// Skip issuer.
   104  	if !tbs.SkipASN1(cryptobyte_asn1.SEQUENCE) {
   105  		return &lintFail
   106  	}
   107  
   108  	// Read thisUpdate.
   109  	var thisUpdate cryptobyte.String
   110  	var thisUpdateTag cryptobyte_asn1.Tag
   111  	if !tbs.ReadAnyASN1Element(&thisUpdate, &thisUpdateTag) {
   112  		return &lintFail
   113  	}
   114  
   115  	// Lint thisUpdate.
   116  	err := lintTimestamp(&thisUpdate, thisUpdateTag)
   117  	if err != nil {
   118  		return &lint.LintResult{Status: lint.Error, Details: err.Error()}
   119  	}
   120  
   121  	// Peek (optional) nextUpdate.
   122  	if tbs.PeekASN1Tag(cryptobyte_asn1.UTCTime) || tbs.PeekASN1Tag(cryptobyte_asn1.GeneralizedTime) {
   123  		// Read nextUpdate.
   124  		var nextUpdate cryptobyte.String
   125  		var nextUpdateTag cryptobyte_asn1.Tag
   126  		if !tbs.ReadAnyASN1Element(&nextUpdate, &nextUpdateTag) {
   127  			return &lintFail
   128  		}
   129  
   130  		// Lint nextUpdate.
   131  		err = lintTimestamp(&nextUpdate, nextUpdateTag)
   132  		if err != nil {
   133  			return &lint.LintResult{Status: lint.Error, Details: err.Error()}
   134  		}
   135  	}
   136  
   137  	// Peek (optional) revokedCertificates.
   138  	if tbs.PeekASN1Tag(cryptobyte_asn1.SEQUENCE) {
   139  		// Read sequence of revokedCertificate.
   140  		var revokedSeq cryptobyte.String
   141  		if !tbs.ReadASN1(&revokedSeq, cryptobyte_asn1.SEQUENCE) {
   142  			return &lintFail
   143  		}
   144  
   145  		// Iterate over each revokedCertificate sequence.
   146  		for !revokedSeq.Empty() {
   147  			// Read revokedCertificate.
   148  			var certSeq cryptobyte.String
   149  			if !revokedSeq.ReadASN1Element(&certSeq, cryptobyte_asn1.SEQUENCE) {
   150  				return &lintFail
   151  			}
   152  
   153  			if !certSeq.ReadASN1(&certSeq, cryptobyte_asn1.SEQUENCE) {
   154  				return &lintFail
   155  			}
   156  
   157  			// Skip userCertificate (serial number).
   158  			if !certSeq.SkipASN1(cryptobyte_asn1.INTEGER) {
   159  				return &lintFail
   160  			}
   161  
   162  			// Read revocationDate.
   163  			var revocationDate cryptobyte.String
   164  			var revocationDateTag cryptobyte_asn1.Tag
   165  			if !certSeq.ReadAnyASN1Element(&revocationDate, &revocationDateTag) {
   166  				return &lintFail
   167  			}
   168  
   169  			// Lint revocationDate.
   170  			err = lintTimestamp(&revocationDate, revocationDateTag)
   171  			if err != nil {
   172  				return &lint.LintResult{Status: lint.Error, Details: err.Error()}
   173  			}
   174  		}
   175  	}
   176  	return &lint.LintResult{Status: lint.Pass}
   177  }
   178  
   179  func lintTimestamp(der *cryptobyte.String, tag cryptobyte_asn1.Tag) error {
   180  	// Preserve the original timestamp for length checking.
   181  	derBytes := *der
   182  	var tsBytes cryptobyte.String
   183  	if !derBytes.ReadASN1(&tsBytes, tag) {
   184  		return errors.New("failed to read timestamp")
   185  	}
   186  	tsLen := len(string(tsBytes))
   187  
   188  	var parsedTime time.Time
   189  	switch tag {
   190  	case cryptobyte_asn1.UTCTime:
   191  		// Verify that the timestamp is properly formatted.
   192  		if tsLen != len(utcTimeFormat) {
   193  			return fmt.Errorf("timestamps encoded using UTCTime MUST be specified in the format %q", utcTimeFormat)
   194  		}
   195  
   196  		if !der.ReadASN1UTCTime(&parsedTime) {
   197  			return errors.New("failed to read timestamp encoded using UTCTime")
   198  		}
   199  
   200  		// Verify that the timestamp is prior to the year 2050. This should
   201  		// really never happen.
   202  		if parsedTime.Year() > 2049 {
   203  			return errors.New("ReadASN1UTCTime returned a UTCTime after 2049")
   204  		}
   205  	case cryptobyte_asn1.GeneralizedTime:
   206  		// Verify that the timestamp is properly formatted.
   207  		if tsLen != len(generalizedTimeFormat) {
   208  			return fmt.Errorf(
   209  				"timestamps encoded using GeneralizedTime MUST be specified in the format %q", generalizedTimeFormat,
   210  			)
   211  		}
   212  
   213  		if !der.ReadASN1GeneralizedTime(&parsedTime) {
   214  			return fmt.Errorf("failed to read timestamp encoded using GeneralizedTime")
   215  		}
   216  
   217  		// Verify that the timestamp occurred after the year 2049.
   218  		if parsedTime.Year() < 2050 {
   219  			return errors.New("timestamps prior to 2050 MUST be encoded using UTCTime")
   220  		}
   221  	default:
   222  		return errors.New("unsupported time format")
   223  	}
   224  
   225  	// Verify that the location is UTC.
   226  	if parsedTime.Location() != time.UTC {
   227  		return errors.New("time must be in UTC")
   228  	}
   229  	return nil
   230  }
   231  

View as plain text