github.com/letsencrypt/boulder@v0.20251208.0/linter/lints/rfc/lint_crl_has_valid_timestamps.go (about) 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 }