Skip to content

Commit ee3ab84

Browse files
authored
Add lint to check that revoked certificates in a CRL has revocation time before or equal to thisUpdate. (#965)
1 parent 09caaf7 commit ee3ab84

5 files changed

+152
-0
lines changed
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* ZLint Copyright 2024 Regents of the University of Michigan
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy
6+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
11+
* implied. See the License for the specific language governing
12+
* permissions and limitations under the License.
13+
*/
14+
15+
package rfc
16+
17+
import (
18+
"fmt"
19+
"time"
20+
21+
"github.com/zmap/zcrypto/x509"
22+
"github.com/zmap/zlint/v3/lint"
23+
"github.com/zmap/zlint/v3/util"
24+
)
25+
26+
/*
27+
* The thisUpdate field in a CRL indicates the time at which the CRL was issued.
28+
* For each entry in the revokedCertificates list within the CRL, there is a
29+
* revocationDate field. This revocationDate signifies when the certificate was revoked.
30+
* Logically, a certificate cannot be listed as revoked on a CRL with a revocationDate
31+
* that is after the thisUpdate of that CRL, because thisUpdate marks the point
32+
* in time that the information in the CRL is considered valid. If a revocation
33+
* happened after the CRL was issued, it would appear on a subsequent CRL.
34+
*/
35+
36+
func init() {
37+
lint.RegisterRevocationListLint(&lint.RevocationListLint{
38+
LintMetadata: lint.LintMetadata{
39+
Name: "e_crl_revocation_time_after_this_update",
40+
Description: "All revocation times for revoked certificates must be on or before the thisUpdate field of the CRL.",
41+
Citation: "RFC 5280: Section 5.1.2.4 & 5.1.2.6",
42+
Source: lint.RFC5280,
43+
EffectiveDate: util.RFC5280Date,
44+
},
45+
Lint: func() lint.RevocationListLintInterface { return &revocationTimeNotAfterThisUpdate{} },
46+
})
47+
}
48+
49+
type revocationTimeNotAfterThisUpdate struct{}
50+
51+
// CheckApplies returns true if the CRL has any revoked certificates.
52+
func (l *revocationTimeNotAfterThisUpdate) CheckApplies(c *x509.RevocationList) bool {
53+
return len(c.RevokedCertificates) > 0
54+
}
55+
56+
// Execute checks that for each revoked certificate, the revocation time is not after the CRL's thisUpdate time.
57+
func (l *revocationTimeNotAfterThisUpdate) Execute(c *x509.RevocationList) *lint.LintResult {
58+
for _, rc := range c.RevokedCertificates {
59+
if rc.RevocationTime.After(c.ThisUpdate) {
60+
return &lint.LintResult{
61+
Status: lint.Error,
62+
Details: fmt.Sprintf("revoked certificate with serial number %x has a revocation time (%s) after the CRL's thisUpdate time (%s)",
63+
rc.SerialNumber, rc.RevocationTime.Format(time.RFC3339), c.ThisUpdate.Format(time.RFC3339)),
64+
}
65+
}
66+
}
67+
return &lint.LintResult{Status: lint.Pass}
68+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* ZLint Copyright 2024 Regents of the University of Michigan
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy
6+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
11+
* implied. See the License for the specific language governing
12+
* permissions and limitations under the License.
13+
*/
14+
15+
package rfc
16+
17+
import (
18+
"testing"
19+
20+
"github.com/zmap/zlint/v3/lint"
21+
"github.com/zmap/zlint/v3/test"
22+
)
23+
24+
func TestRevocationTimeNotAfterThisUpdate(t *testing.T) {
25+
t.Parallel()
26+
testCases := []struct {
27+
name string
28+
path string
29+
want lint.LintStatus
30+
}{
31+
{
32+
name: "crl revocation time before thisUpdate",
33+
path: "crl_revocation_time_before_this_update.pem",
34+
want: lint.Pass,
35+
},
36+
{
37+
name: "crl revocation time equals thisUpdate",
38+
path: "crl_revocation_time_equals_this_update.pem",
39+
want: lint.Pass,
40+
},
41+
{
42+
name: "crl revocation time after thisUpdate",
43+
path: "crl_revocation_time_after_this_update.pem",
44+
want: lint.Error,
45+
},
46+
{
47+
name: "CRL with no revoked certificates",
48+
path: "crl_no_revoked_certs.pem",
49+
want: lint.NA,
50+
},
51+
}
52+
53+
for _, tc := range testCases {
54+
tc := tc
55+
t.Run(tc.name, func(t *testing.T) {
56+
t.Parallel()
57+
out := test.TestRevocationListLint(t, "e_crl_revocation_time_after_this_update", tc.path)
58+
if out.Status != tc.want {
59+
t.Errorf("expected status %s for %s, got %s", tc.want, tc.path, out.Status)
60+
}
61+
})
62+
}
63+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-----BEGIN X509 CRL-----
2+
MIHlMIGLAgEBMAoGCCqGSM49BAMCMAAXDTI0MTAyNDE0MTA0OFoXDTI0MTAyNDE1
3+
MTA0OFowIjAgAgEDFw0yNDEwMjQxNTEwNDhaMAwwCgYDVR0VBAMKAQWgNjA0MCYG
4+
A1UdIwQfMB2AG2ludGVybWVkaWF0ZSBzdWJqZWN0IGtleSBpZDAKBgNVHRQEAwIB
5+
ATAKBggqhkjOPQQDAgNJADBGAiEA7vnPX4zBzxY6uRiNE0kPYq8CMo246nmxuJlE
6+
HhKcH2cCIQCtrgybUVLaC0tqiwkUtFeedn5GBCogtSFModkPa8662A==
7+
-----END X509 CRL-----
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-----BEGIN X509 CRL-----
2+
MIHjMIGLAgEBMAoGCCqGSM49BAMCMAAXDTI0MTAyNDE0MTEyMFoXDTI0MTAyNDE1
3+
MTEyMFowIjAgAgEDFw0yNDEwMjQxMzExMjBaMAwwCgYDVR0VBAMKAQWgNjA0MCYG
4+
A1UdIwQfMB2AG2ludGVybWVkaWF0ZSBzdWJqZWN0IGtleSBpZDAKBgNVHRQEAwIB
5+
ATAKBggqhkjOPQQDAgNHADBEAiBa2pXuN5xZSM3GiJv4JxzJZQfpMNimM4z/6c8r
6+
Ci6YgAIgIZ8RcgAcn/7KYKyRrDzw2anjMznLUuUzhHTMaJ9si6g=
7+
-----END X509 CRL-----
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-----BEGIN X509 CRL-----
2+
MIHkMIGLAgEBMAoGCCqGSM49BAMCMAAXDTI0MTAyNDE4MjQxNVoXDTI0MTAyNDE5
3+
MjQxNVowIjAgAgEDFw0yNDEwMjQxODI0MTVaMAwwCgYDVR0VBAMKAQWgNjA0MCYG
4+
A1UdIwQfMB2AG2ludGVybWVkaWF0ZSBzdWJqZWN0IGtleSBpZDAKBgNVHRQEAwIB
5+
ATAKBggqhkjOPQQDAgNIADBFAiEA7w9yNFeGW2QMQqxByovBSHXvAgpW/M7pF0VN
6+
anEwX20CIFlrTKkTDCGkvh2gc0rTR+e9RC/qhiJLMBVjos+GSM+6
7+
-----END X509 CRL-----

0 commit comments

Comments
 (0)