...
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
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
88 var tbs cryptobyte.String
89 if !input.ReadASN1(&tbs, cryptobyte_asn1.SEQUENCE) {
90 return &lintFail
91 }
92
93
94 if !tbs.SkipOptionalASN1(cryptobyte_asn1.INTEGER) {
95 return &lintFail
96 }
97
98
99 if !tbs.SkipASN1(cryptobyte_asn1.SEQUENCE) {
100 return &lintFail
101 }
102
103
104 if !tbs.SkipASN1(cryptobyte_asn1.SEQUENCE) {
105 return &lintFail
106 }
107
108
109 var thisUpdate cryptobyte.String
110 var thisUpdateTag cryptobyte_asn1.Tag
111 if !tbs.ReadAnyASN1Element(&thisUpdate, &thisUpdateTag) {
112 return &lintFail
113 }
114
115
116 err := lintTimestamp(&thisUpdate, thisUpdateTag)
117 if err != nil {
118 return &lint.LintResult{Status: lint.Error, Details: err.Error()}
119 }
120
121
122 if tbs.PeekASN1Tag(cryptobyte_asn1.UTCTime) || tbs.PeekASN1Tag(cryptobyte_asn1.GeneralizedTime) {
123
124 var nextUpdate cryptobyte.String
125 var nextUpdateTag cryptobyte_asn1.Tag
126 if !tbs.ReadAnyASN1Element(&nextUpdate, &nextUpdateTag) {
127 return &lintFail
128 }
129
130
131 err = lintTimestamp(&nextUpdate, nextUpdateTag)
132 if err != nil {
133 return &lint.LintResult{Status: lint.Error, Details: err.Error()}
134 }
135 }
136
137
138 if tbs.PeekASN1Tag(cryptobyte_asn1.SEQUENCE) {
139
140 var revokedSeq cryptobyte.String
141 if !tbs.ReadASN1(&revokedSeq, cryptobyte_asn1.SEQUENCE) {
142 return &lintFail
143 }
144
145
146 for !revokedSeq.Empty() {
147
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
158 if !certSeq.SkipASN1(cryptobyte_asn1.INTEGER) {
159 return &lintFail
160 }
161
162
163 var revocationDate cryptobyte.String
164 var revocationDateTag cryptobyte_asn1.Tag
165 if !certSeq.ReadAnyASN1Element(&revocationDate, &revocationDateTag) {
166 return &lintFail
167 }
168
169
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
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
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
201
202 if parsedTime.Year() > 2049 {
203 return errors.New("ReadASN1UTCTime returned a UTCTime after 2049")
204 }
205 case cryptobyte_asn1.GeneralizedTime:
206
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
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
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