1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package main
18
19 import (
20 "flag"
21 "fmt"
22 "io"
23 "net/http"
24 "os"
25 "time"
26
27 "github.com/google/certificate-transparency-go/x509"
28 "github.com/google/certificate-transparency-go/x509util"
29 "k8s.io/klog/v2"
30 )
31
32 var (
33 caFile = flag.String("ca", "", "CA certificate file")
34 strict = flag.Bool("strict", false, "Strict validation of CRL contents")
35 expectCerts = flag.Bool("cert", false, "Input files are certificates not CRLs")
36 )
37
38 func main() {
39 klog.InitFlags(nil)
40 flag.Parse()
41
42
43 var caCerts []*x509.Certificate
44 if *caFile != "" {
45 caDataList, err := x509util.ReadPossiblePEMFile(*caFile, "CERTIFICATE")
46 if err != nil {
47 klog.Exitf("%s: failed to read CA cert data: %v", *caFile, err)
48 }
49 for _, caData := range caDataList {
50 certs, err := x509.ParseCertificates(caData)
51 if err != nil {
52 klog.Errorf("%s: %v", *caFile, err)
53 }
54 if len(certs) == 0 {
55 klog.Errorf("%s: no certificates found", *caFile)
56 }
57 caCerts = append(caCerts, certs[0])
58 }
59 }
60
61 errored := false
62 for _, arg := range flag.Args() {
63 if *expectCerts {
64 if err := processCertArg(arg, caCerts); err != nil {
65 klog.Errorf("%s: failed to read certificate data: %v", arg, err)
66 errored = true
67 }
68 } else {
69 if err := processCRLArg(arg, caCerts); err != nil {
70 klog.Errorf("%s: failed to read CRL data: %v", arg, err)
71 errored = true
72 }
73 }
74 }
75
76 if errored {
77 os.Exit(1)
78 }
79 }
80
81 func processCRLArg(arg string, caCerts []*x509.Certificate) error {
82 dataList, err := x509util.ReadPossiblePEMURL(arg, "X509 CRL")
83 if err != nil {
84 return err
85 }
86 for _, data := range dataList {
87 if _, err := processCRL(data, caCerts); err != nil {
88 return err
89 }
90 }
91 return nil
92 }
93
94 func processCRL(data []byte, caCerts []*x509.Certificate) (*x509.CertificateList, error) {
95 certList, err := x509.ParseCertificateListDER(data)
96 if certList == nil {
97 return nil, fmt.Errorf("CRL parse error: %v", err)
98 }
99 if err != nil && *strict {
100 return nil, fmt.Errorf("strict CRL parse error: %v", err)
101 }
102 klog.Infof("Processing CRL:\n%s", x509util.CRLToString(certList))
103
104 verified := false
105 if len(caCerts) == 0 {
106 klog.Warningf("Skipping signature validation as no CA certs available")
107 verified = true
108 }
109 var verifyErr error
110 for _, caCert := range caCerts {
111 if err := caCert.CheckCertificateListSignature(certList); err != nil {
112 verifyErr = err
113 } else {
114 klog.Infof("CRL signature verified against CA cert %q", x509util.NameToString(caCert.Subject))
115 verifyErr = nil
116 verified = true
117 break
118 }
119 }
120 if !verified {
121 return nil, fmt.Errorf("verification error: %v", verifyErr)
122 }
123 return certList, nil
124 }
125
126 func processCertArg(filename string, caCerts []*x509.Certificate) error {
127 dataList, err := x509util.ReadPossiblePEMFile(filename, "CERTIFICATE")
128 if err != nil {
129 return err
130 }
131 if len(dataList) == 0 {
132 return fmt.Errorf("no certs found in %s", filename)
133 }
134
135 if len(caCerts) == 0 {
136
137 for i := 1; i < len(dataList); i++ {
138 issuer, err := x509.ParseCertificate(dataList[i])
139 if err != nil {
140 klog.Warningf("Failed to parse [%d] in chain: %v", i, err)
141 continue
142 }
143 klog.Infof("Treating cert [%d] with subject %q as potential issuer", i, x509util.NameToString(issuer.Subject))
144 caCerts = append(caCerts, issuer)
145 }
146 }
147 return processCert(dataList[0], caCerts)
148 }
149
150 func processCert(data []byte, caCerts []*x509.Certificate) error {
151 client := &http.Client{}
152
153 cert, err := x509.ParseCertificate(data)
154 if err != nil {
155 return fmt.Errorf("certificate parse error: %v", err)
156 }
157 issuer, err := x509util.GetIssuer(cert, client)
158 if err != nil {
159 klog.Warningf("Failed to retrieve issuer for cert: %v", err)
160 }
161 if issuer != nil {
162 klog.Infof("Using issuer %q", x509util.NameToString(issuer.Subject))
163 caCerts = append(caCerts, issuer)
164 }
165 expired := false
166 if time.Now().After(cert.NotAfter) {
167 klog.Errorf("Certificate is expired (since %v)", cert.NotAfter)
168 expired = true
169 }
170 for _, crldp := range cert.CRLDistributionPoints {
171 klog.Infof("Retrieving CRL from %q", crldp)
172 rsp, err := client.Get(crldp)
173 if err != nil || rsp.StatusCode != http.StatusOK {
174 return fmt.Errorf("failed to get CRL from %q: %v", crldp, err)
175 }
176 body, err := io.ReadAll(rsp.Body)
177 if err != nil {
178 return fmt.Errorf("failed to read CRL from %q: %v", crldp, err)
179 }
180 rsp.Body.Close()
181 certList, err := processCRL(body, caCerts)
182 if err != nil {
183 return err
184 }
185 if expired {
186 continue
187 }
188
189 for _, rev := range certList.TBSCertList.RevokedCertificates {
190 if rev.SerialNumber.Cmp(cert.SerialNumber) == 0 {
191 klog.Errorf("%s: certificate with serial number %v revoked at %v", crldp, cert.SerialNumber, rev.RevocationTime)
192 if rev.RevocationReason != x509.Unspecified {
193 klog.Errorf(" revocation reason: %s\v", x509util.RevocationReasonToString(rev.RevocationReason))
194 }
195 break
196 }
197 }
198 }
199
200 return nil
201 }
202
View as plain text