...

Source file src/github.com/google/certificate-transparency-go/x509util/certcheck/certcheck.go

Documentation: github.com/google/certificate-transparency-go/x509util/certcheck

     1  // Copyright 2016 Google LLC. 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.
    16  package main
    17  
    18  import (
    19  	"bytes"
    20  	"crypto/tls"
    21  	"flag"
    22  	"fmt"
    23  	"net/url"
    24  	"os"
    25  	"strings"
    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  	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  	strict                   = flag.Bool("strict", true, "Set non-zero exit code for non-fatal errors in parsing")
    38  	validate                 = flag.Bool("validate", false, "Validate certificate signatures")
    39  	checkTime                = flag.Bool("check_time", false, "Check current validity of certificate")
    40  	checkName                = flag.Bool("check_name", true, "Check certificate name validity")
    41  	checkEKU                 = flag.Bool("check_eku", true, "Check EKU nesting validity")
    42  	checkPathLen             = flag.Bool("check_path_len", true, "Check path len constraint validity")
    43  	checkNameConstraint      = flag.Bool("check_name_constraint", true, "Check name constraints")
    44  	checkUnknownCriticalExts = flag.Bool("check_unknown_critical_exts", true, "Check for unknown critical extensions")
    45  	checkRevoked             = flag.Bool("check_revocation", false, "Check revocation status of certificate")
    46  )
    47  
    48  func addCerts(filename string, pool *x509.CertPool) {
    49  	if filename != "" {
    50  		dataList, err := x509util.ReadPossiblePEMFile(filename, "CERTIFICATE")
    51  		if err != nil {
    52  			klog.Exitf("Failed to read certificate file: %v", err)
    53  		}
    54  		for _, data := range dataList {
    55  			certs, err := x509.ParseCertificates(data)
    56  			if err != nil {
    57  				klog.Exitf("Failed to parse certificate from %s: %v", filename, err)
    58  			}
    59  			for _, cert := range certs {
    60  				pool.AddCert(cert)
    61  			}
    62  		}
    63  	}
    64  }
    65  
    66  func main() {
    67  	klog.InitFlags(nil)
    68  	flag.Parse()
    69  
    70  	failed := false
    71  	for _, target := range flag.Args() {
    72  		var err error
    73  		var chain []*x509.Certificate
    74  		if strings.HasPrefix(target, "https://") {
    75  			chain, err = chainFromSite(target)
    76  		} else {
    77  			chain, err = chainFromFile(target)
    78  		}
    79  		if err != nil {
    80  			klog.Errorf("%v", err)
    81  		}
    82  		if x509.IsFatal(err) {
    83  			failed = true
    84  			continue
    85  		} else if err != nil && *strict {
    86  			failed = true
    87  		}
    88  		for _, cert := range chain {
    89  			if *verbose {
    90  				fmt.Print(x509util.CertificateToString(cert))
    91  			}
    92  			if *checkRevoked {
    93  				if err := checkRevocation(cert, *verbose); err != nil {
    94  					klog.Errorf("%s: certificate is revoked: %v", target, err)
    95  					failed = true
    96  				}
    97  			}
    98  		}
    99  		if *validate && len(chain) > 0 {
   100  			opts := x509.VerifyOptions{
   101  				DisableTimeChecks:              !*checkTime,
   102  				DisableCriticalExtensionChecks: !*checkUnknownCriticalExts,
   103  				DisableNameChecks:              !*checkName,
   104  				DisableEKUChecks:               !*checkEKU,
   105  				DisablePathLenChecks:           !*checkPathLen,
   106  				DisableNameConstraintChecks:    !*checkNameConstraint,
   107  			}
   108  			if err := validateChain(chain, opts, *root, *intermediate, *useSystemRoots); err != nil {
   109  				klog.Errorf("%s: verification error: %v", target, err)
   110  				failed = true
   111  			}
   112  		}
   113  	}
   114  	if failed {
   115  		os.Exit(1)
   116  	}
   117  }
   118  
   119  // chainFromSite retrieves the certificate chain from an https: URL.
   120  // Note that both a chain and an error can be returned (in which case
   121  // the error will be of type x509.NonFatalErrors).
   122  func chainFromSite(target string) ([]*x509.Certificate, error) {
   123  	u, err := url.Parse(target)
   124  	if err != nil {
   125  		return nil, fmt.Errorf("%s: failed to parse URL: %v", target, err)
   126  	}
   127  	if u.Scheme != "https" {
   128  		return nil, fmt.Errorf("%s: non-https URL provided", target)
   129  	}
   130  	host := u.Host
   131  	if !strings.Contains(host, ":") {
   132  		host += ":443"
   133  	}
   134  
   135  	// Insecure TLS connection here so we can always proceed.
   136  	conn, err := tls.Dial("tcp", host, &tls.Config{InsecureSkipVerify: true})
   137  	if err != nil {
   138  		return nil, fmt.Errorf("%s: failed to dial %q: %v", target, host, err)
   139  	}
   140  	defer conn.Close()
   141  
   142  	// Convert base crypto/x509.Certificates to our forked x509.Certificate type.
   143  	goChain := conn.ConnectionState().PeerCertificates
   144  	var nfe *x509.NonFatalErrors
   145  	chain := make([]*x509.Certificate, len(goChain))
   146  	for i, goCert := range goChain {
   147  		cert, err := x509.ParseCertificate(goCert.Raw)
   148  		if x509.IsFatal(err) {
   149  			return nil, fmt.Errorf("%s: failed to convert Go Certificate [%d]: %v", target, i, err)
   150  		} else if errs, ok := err.(x509.NonFatalErrors); ok {
   151  			nfe = nfe.Append(&errs)
   152  		} else if err != nil {
   153  			return nil, fmt.Errorf("%s: failed to convert Go Certificate [%d]: %v", target, i, err)
   154  		}
   155  		chain[i] = cert
   156  	}
   157  
   158  	if nfe.HasError() {
   159  		return chain, *nfe
   160  	}
   161  	return chain, nil
   162  }
   163  
   164  // chainFromSite retrieves a certificate chain from a PEM file.
   165  // Note that both a chain and an error can be returned (in which case
   166  // the error will be of type x509.NonFatalErrors).
   167  func chainFromFile(filename string) ([]*x509.Certificate, error) {
   168  	dataList, err := x509util.ReadPossiblePEMFile(filename, "CERTIFICATE")
   169  	if err != nil {
   170  		return nil, fmt.Errorf("%s: failed to read data: %v", filename, err)
   171  	}
   172  	var nfe *x509.NonFatalErrors
   173  	var chain []*x509.Certificate
   174  	for _, data := range dataList {
   175  		certs, err := x509.ParseCertificates(data)
   176  		if x509.IsFatal(err) {
   177  			return nil, fmt.Errorf("%s: failed to parse: %v", filename, err)
   178  		} else if errs, ok := err.(x509.NonFatalErrors); ok {
   179  			nfe = nfe.Append(&errs)
   180  		} else if err != nil {
   181  			return nil, fmt.Errorf("%s: failed to parse: %v", filename, err)
   182  		}
   183  		chain = append(chain, certs...)
   184  	}
   185  	if nfe.HasError() {
   186  		return chain, *nfe
   187  	}
   188  	return chain, nil
   189  }
   190  
   191  func validateChain(chain []*x509.Certificate, opts x509.VerifyOptions, rootsFile, intermediatesFile string, useSystemRoots bool) error {
   192  	roots := x509.NewCertPool()
   193  	if useSystemRoots {
   194  		systemRoots, err := x509.SystemCertPool()
   195  		if err != nil {
   196  			klog.Errorf("Failed to get system roots: %v", err)
   197  		}
   198  		roots = systemRoots
   199  	}
   200  	opts.KeyUsages = []x509.ExtKeyUsage{x509.ExtKeyUsageAny}
   201  	opts.Roots = roots
   202  	opts.Intermediates = x509.NewCertPool()
   203  	addCerts(rootsFile, opts.Roots)
   204  	addCerts(intermediatesFile, opts.Intermediates)
   205  
   206  	if !useSystemRoots && len(rootsFile) == 0 {
   207  		// No root CA certs provided, so assume the chain is self-contained.
   208  		if len(chain) > 1 {
   209  			last := chain[len(chain)-1]
   210  			if bytes.Equal(last.RawSubject, last.RawIssuer) {
   211  				opts.Roots.AddCert(last)
   212  			}
   213  		}
   214  	}
   215  	if len(intermediatesFile) == 0 {
   216  		// No intermediate CA certs provided, so assume later entries in the chain are intermediates.
   217  		for i := 1; i < len(chain); i++ {
   218  			opts.Intermediates.AddCert(chain[i])
   219  		}
   220  	}
   221  	_, err := chain[0].Verify(opts)
   222  	return err
   223  }
   224  
   225  func checkRevocation(cert *x509.Certificate, verbose bool) error {
   226  	for _, crldp := range cert.CRLDistributionPoints {
   227  		crlDataList, err := x509util.ReadPossiblePEMURL(crldp, "X509 CRL")
   228  		if err != nil {
   229  			klog.Errorf("failed to retrieve CRL from %q: %v", crldp, err)
   230  			continue
   231  		}
   232  		for _, crlData := range crlDataList {
   233  			crl, err := x509.ParseCertificateList(crlData)
   234  			if x509.IsFatal(err) {
   235  				klog.Errorf("failed to parse CRL from %q: %v", crldp, err)
   236  				continue
   237  			}
   238  			if err != nil {
   239  				klog.Errorf("non-fatal error parsing CRL from %q: %v", crldp, err)
   240  			}
   241  			if verbose {
   242  				fmt.Printf("\nRevocation data from %s:\n", crldp)
   243  				fmt.Print(x509util.CRLToString(crl))
   244  			}
   245  			for _, c := range crl.TBSCertList.RevokedCertificates {
   246  				if c.SerialNumber.Cmp(cert.SerialNumber) == 0 {
   247  					return fmt.Errorf("certificate is revoked since %v", c.RevocationTime)
   248  				}
   249  			}
   250  		}
   251  	}
   252  	return nil
   253  }
   254  

View as plain text