Skip to content

Commit 597a098

Browse files
Zlint CLI supports linting ocsp responses (#993)
* Zlint CLI supports linting ocsp responses This also updates OCSP testing code to support pem, der encoding * Fix lint error * Report error message * OCSP responses are not encoded in PEM format --------- Co-authored-by: Christopher Henderson <[email protected]>
1 parent 30a1e16 commit 597a098

File tree

5 files changed

+52
-35
lines changed

5 files changed

+52
-35
lines changed

v3/cmd/zlint/main.go

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232
"github.com/zmap/zlint/v3"
3333
"github.com/zmap/zlint/v3/formattedoutput"
3434
"github.com/zmap/zlint/v3/lint"
35+
"golang.org/x/crypto/ocsp"
3536

3637
_ "github.com/zmap/zlint/v3/profiles"
3738
)
@@ -169,19 +170,14 @@ func doLint(inputFile *os.File, inform string, registry lint.Registry) {
169170
}
170171

171172
var asn1Data []byte
172-
var isCRL bool
173173
switch inform {
174174
case "pem":
175175
p, _ := pem.Decode(fileBytes)
176176
if p == nil {
177177
log.Fatal("unable to parse PEM")
178178
}
179-
switch p.Type {
180-
case "CERTIFICATE":
181-
case "X509 CRL":
182-
isCRL = true
183-
default:
184-
log.Fatalf("unknown PEM type (%s)", p.Type)
179+
if p.Type != "CERTIFICATE" && p.Type != "X509 CRL" {
180+
log.Fatalf("unknown PEM type %s", p.Type)
185181
}
186182
asn1Data = p.Bytes
187183
case "der":
@@ -195,18 +191,28 @@ func doLint(inputFile *os.File, inform string, registry lint.Registry) {
195191
log.Fatalf("unknown input format %s", format)
196192
}
197193
var zlintResult *zlint.ResultSet
198-
if isCRL {
199-
crl, err := x509.ParseRevocationList(asn1Data)
200-
if err != nil {
201-
log.Fatalf("unable to parse certificate revocation list: %s", err)
202-
}
203-
zlintResult = zlint.LintRevocationListEx(crl, registry)
194+
var errs []error
195+
if cert, err := x509.ParseCertificate(asn1Data); err == nil {
196+
zlintResult = zlint.LintCertificateEx(cert, registry)
204197
} else {
205-
c, err := x509.ParseCertificate(asn1Data)
206-
if err != nil {
207-
log.Fatalf("unable to parse certificate: %s", err)
198+
errs = append(errs, fmt.Errorf("parsing as certificate: %v", err))
199+
}
200+
if zlintResult == nil {
201+
if crl, err := x509.ParseRevocationList(asn1Data); err == nil {
202+
zlintResult = zlint.LintRevocationListEx(crl, registry)
203+
} else {
204+
errs = append(errs, fmt.Errorf("parsing as CRL: %v", err))
208205
}
209-
zlintResult = zlint.LintCertificateEx(c, registry)
206+
}
207+
if zlintResult == nil {
208+
if resp, err := ocsp.ParseResponse(asn1Data, nil); err == nil {
209+
zlintResult = zlint.LintOcspResponseEx(resp, registry)
210+
} else {
211+
errs = append(errs, fmt.Errorf("parsing as OCSP response: %v", err))
212+
}
213+
}
214+
if zlintResult == nil {
215+
log.Fatalf("unable to parse input as any known type, errors: %v", errs)
210216
}
211217
jsonBytes, err := json.Marshal(zlintResult.Results)
212218
if err != nil {

v3/lints/rfc/lint_ocsp_this_update_not_after_produced_at_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,14 @@ func TestOCSPThisUpdateNotAfterProducedAt(t *testing.T) {
3434
inputPath: "ocspThisUpdateAfterProducedAt",
3535
want: lint.Error,
3636
},
37+
{
38+
inputPath: "ocspThisUpdateNotAfterProducedAt.der",
39+
want: lint.Pass,
40+
},
41+
{
42+
inputPath: "ocspThisUpdateAfterProducedAt.der",
43+
want: lint.Error,
44+
},
3745
}
3846
for _, tc := range cases {
3947
t.Run(tc.inputPath, func(t *testing.T) {

v3/test/helpers.go

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -218,13 +218,14 @@ func ReadTestRevocationList(tb testing.TB, inPath string) *x509.RevocationList {
218218

219219
if strings.Contains(string(data), "-BEGIN X509 CRL-") {
220220
block, _ := pem.Decode(data)
221-
if block == nil { //nolint: staticcheck // tb.Fatalf exits
221+
if block == nil {
222222
tb.Fatalf(
223223
"Failed to PEM decode test revocation list from %q - "+
224224
"Does a unit test have a buggy test cert file?\n",
225225
fullPath)
226+
} else {
227+
data = block.Bytes
226228
}
227-
data = block.Bytes //nolint: staticcheck // tb.Fatalf exits
228229
}
229230

230231
theCrl, err := x509.ParseRevocationList(data)
@@ -238,28 +239,30 @@ func ReadTestRevocationList(tb testing.TB, inPath string) *x509.RevocationList {
238239
return theCrl
239240
}
240241

241-
// ReadTestOCSPResponse loads a ocsp.Response from the given inPath which is assumed
242-
// to be relative to `testdata/`. The OCSP file must contain the OCSP response in
243-
// Base64 encoding. openssl ocsp -resp_text -respin <(base64 -d the_filename)
244-
// To decode it using OpenSSL, store the base64-encoded in string in a file, and run:
245-
// Important: ReadTestOCSPResponse is only appropriate for unit tests. It will panic if
246-
// the inPath file can not be loaded.
242+
// ReadTestOCSPResponse loads an OCSP response from the given inPath, which should be
243+
// relative to `testdata/`. If the filename ends with `.der`, the file is treated as a
244+
// binary DER-encoded OCSP response. Otherwise, the file is expected to contain a base64-encoded
245+
// OCSP response.
246+
//
247+
// This function is intended for use in unit tests only. It will call tb.Fatalf if the file cannot
248+
// be read, decoded, or parsed.
247249
func ReadTestOCSPResponse(tb testing.TB, inPath string) *ocsp.Response {
248250
tb.Helper()
249251
fullPath := "../../testdata/" + inPath
250-
base64Data, err := os.ReadFile(fullPath)
252+
fileContent, err := os.ReadFile(fullPath)
251253
if err != nil {
252254
tb.Fatalf("Failed to read file: %v", err)
253255
}
254-
data, err := base64.StdEncoding.DecodeString(string(base64Data))
255-
if err != nil {
256-
tb.Fatalf("Failed to decode base64 data: %v", err)
257-
}
258-
if err != nil {
259-
tb.Fatalf(
260-
"Unable to read test ocsp response from %q - %q "+
261-
"Does a unit test have an incorrect test file name?\n",
262-
fullPath, err)
256+
257+
var data []byte
258+
switch {
259+
case strings.HasSuffix(inPath, ".der"):
260+
data = fileContent
261+
default:
262+
data, err = base64.StdEncoding.DecodeString(string(fileContent))
263+
if err != nil {
264+
tb.Fatalf("Failed to decode base64 data: %v", err)
265+
}
263266
}
264267

265268
theOcspResponse, err := ocsp.ParseResponse(data, nil)
241 Bytes
Binary file not shown.
241 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)