1// Copyright 2016 Google Inc. All Rights Reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15// certcheck is a utility to show and check the contents of certificates. 16package main 17 18import ( 19 "bytes" 20 "crypto/tls" 21 "flag" 22 "fmt" 23 "net/url" 24 "os" 25 "strings" 26 27 "github.com/golang/glog" 28 "github.com/google/certificate-transparency-go/x509" 29 "github.com/google/certificate-transparency-go/x509util" 30) 31 32var ( 33 root = flag.String("root", "", "Root CA certificate file") 34 intermediate = flag.String("intermediate", "", "Intermediate CA certificate file") 35 useSystemRoots = flag.Bool("system_roots", false, "Use system roots") 36 verbose = flag.Bool("verbose", false, "Verbose output") 37 validate = flag.Bool("validate", false, "Validate certificate signatures") 38 timecheck = flag.Bool("timecheck", false, "Check current validity of certificate") 39 revokecheck = flag.Bool("check_revocation", false, "Check revocation status of certificate") 40 ignoreUnknownCriticalExts = flag.Bool("ignore_unknown_critical_exts", false, "Ignore unknown-critical-extension errors") 41) 42 43func addCerts(filename string, pool *x509.CertPool) { 44 if filename != "" { 45 dataList, err := x509util.ReadPossiblePEMFile(filename, "CERTIFICATE") 46 if err != nil { 47 glog.Exitf("Failed to read certificate file: %v", err) 48 } 49 for _, data := range dataList { 50 certs, err := x509.ParseCertificates(data) 51 if err != nil { 52 glog.Exitf("Failed to parse certificate from %s: %v", filename, err) 53 } 54 for _, cert := range certs { 55 pool.AddCert(cert) 56 } 57 } 58 } 59} 60 61func main() { 62 flag.Parse() 63 64 failed := false 65 for _, target := range flag.Args() { 66 var err error 67 var chain []*x509.Certificate 68 if strings.HasPrefix(target, "https://") { 69 chain, err = chainFromSite(target) 70 } else { 71 chain, err = chainFromFile(target) 72 } 73 if err != nil { 74 glog.Errorf("%v", err) 75 failed = true 76 continue 77 } 78 for _, cert := range chain { 79 if *verbose { 80 fmt.Print(x509util.CertificateToString(cert)) 81 } 82 if *revokecheck { 83 if err := checkRevocation(cert, *verbose); err != nil { 84 glog.Errorf("%s: certificate is revoked: %v", target, err) 85 failed = true 86 } 87 } 88 } 89 if *validate && len(chain) > 0 { 90 if *ignoreUnknownCriticalExts { 91 // We don't want failures from Verify due to unknown critical extensions, 92 // so clear them out. 93 for _, cert := range chain { 94 cert.UnhandledCriticalExtensions = nil 95 } 96 } 97 if err := validateChain(chain, *timecheck, *root, *intermediate, *useSystemRoots); err != nil { 98 glog.Errorf("%s: verification error: %v", target, err) 99 failed = true 100 } 101 } 102 } 103 if failed { 104 os.Exit(1) 105 } 106} 107 108func chainFromSite(target string) ([]*x509.Certificate, error) { 109 u, err := url.Parse(target) 110 if err != nil { 111 return nil, fmt.Errorf("%s: failed to parse URL: %v", target, err) 112 } 113 if u.Scheme != "https" { 114 return nil, fmt.Errorf("%s: non-https URL provided", target) 115 } 116 host := u.Host 117 if !strings.Contains(host, ":") { 118 host += ":443" 119 } 120 121 conn, err := tls.Dial("tcp", host, &tls.Config{InsecureSkipVerify: true}) 122 if err != nil { 123 return nil, fmt.Errorf("%s: failed to dial %q: %v", target, host, err) 124 } 125 defer conn.Close() 126 127 // Convert base crypto/x509.Certificates to our forked x509.Certificate type. 128 goChain := conn.ConnectionState().PeerCertificates 129 chain := make([]*x509.Certificate, len(goChain)) 130 for i, goCert := range goChain { 131 cert, err := x509.ParseCertificate(goCert.Raw) 132 if err != nil { 133 return nil, fmt.Errorf("%s: failed to convert Go Certificate [%d]: %v", target, i, err) 134 } 135 chain[i] = cert 136 } 137 138 return chain, nil 139} 140 141func chainFromFile(filename string) ([]*x509.Certificate, error) { 142 dataList, err := x509util.ReadPossiblePEMFile(filename, "CERTIFICATE") 143 if err != nil { 144 return nil, fmt.Errorf("%s: failed to read data: %v", filename, err) 145 } 146 var chain []*x509.Certificate 147 for _, data := range dataList { 148 certs, err := x509.ParseCertificates(data) 149 if x509.IsFatal(err) { 150 return nil, fmt.Errorf("%s: failed to parse: %v", filename, err) 151 } 152 if err != nil { 153 glog.Errorf("%s: non-fatal error parsing: %v", filename, err) 154 } 155 chain = append(chain, certs...) 156 } 157 return chain, nil 158} 159 160func validateChain(chain []*x509.Certificate, timecheck bool, rootsFile, intermediatesFile string, useSystemRoots bool) error { 161 roots := x509.NewCertPool() 162 if useSystemRoots { 163 systemRoots, err := x509.SystemCertPool() 164 if err != nil { 165 glog.Errorf("Failed to get system roots: %v", err) 166 } 167 roots = systemRoots 168 } 169 opts := x509.VerifyOptions{ 170 KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, 171 Roots: roots, 172 Intermediates: x509.NewCertPool(), 173 DisableTimeChecks: !timecheck, 174 } 175 addCerts(rootsFile, opts.Roots) 176 addCerts(intermediatesFile, opts.Intermediates) 177 178 if !useSystemRoots && len(rootsFile) == 0 { 179 // No root CA certs provided, so assume the chain is self-contained. 180 count := len(chain) 181 if len(chain) > 1 { 182 last := chain[len(chain)-1] 183 if bytes.Equal(last.RawSubject, last.RawIssuer) { 184 opts.Roots.AddCert(last) 185 count-- 186 } 187 } 188 } 189 if len(intermediatesFile) == 0 { 190 // No intermediate CA certs provided, so assume later entries in the chain are intermediates. 191 for i := 1; i < len(chain); i++ { 192 opts.Intermediates.AddCert(chain[i]) 193 } 194 } 195 _, err := chain[0].Verify(opts) 196 return err 197} 198 199func checkRevocation(cert *x509.Certificate, verbose bool) error { 200 for _, crldp := range cert.CRLDistributionPoints { 201 crlDataList, err := x509util.ReadPossiblePEMURL(crldp, "X509 CRL") 202 if err != nil { 203 glog.Errorf("failed to retrieve CRL from %q: %v", crldp, err) 204 continue 205 } 206 for _, crlData := range crlDataList { 207 crl, err := x509.ParseCertificateList(crlData) 208 if x509.IsFatal(err) { 209 glog.Errorf("failed to parse CRL from %q: %v", crldp, err) 210 continue 211 } 212 if err != nil { 213 glog.Errorf("non-fatal error parsing CRL from %q: %v", crldp, err) 214 } 215 if verbose { 216 fmt.Printf("\nRevocation data from %s:\n", crldp) 217 fmt.Print(x509util.CRLToString(crl)) 218 } 219 for _, c := range crl.TBSCertList.RevokedCertificates { 220 if c.SerialNumber.Cmp(cert.SerialNumber) == 0 { 221 return fmt.Errorf("certificate is revoked since %v", c.RevocationTime) 222 } 223 } 224 } 225 } 226 return nil 227} 228