github.com/zmap/zlint@v1.1.0/cmd/zlint/main.go (about)

     1  /*
     2   * ZLint Copyright 2017 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 main
    16  
    17  import (
    18  	"bytes"
    19  	"encoding/base64"
    20  	"encoding/json"
    21  	"encoding/pem"
    22  	"flag"
    23  	"fmt"
    24  	"io/ioutil"
    25  	"os"
    26  	"sort"
    27  	"strings"
    28  
    29  	log "github.com/sirupsen/logrus"
    30  	"github.com/zmap/zcrypto/x509"
    31  	"github.com/zmap/zlint"
    32  	"github.com/zmap/zlint/lints"
    33  )
    34  
    35  var ( // flags
    36  	listLintsJSON   bool
    37  	listLintsSchema bool
    38  	prettyprint     bool
    39  	format          string
    40  	include         string
    41  	exclude         string
    42  )
    43  
    44  func init() {
    45  	flag.BoolVar(&listLintsJSON, "list-lints-json", false, "Print supported lints in JSON format, one per line")
    46  	flag.BoolVar(&listLintsSchema, "list-lints-schema", false, "Print supported lints as a ZSchema")
    47  	flag.StringVar(&format, "format", "pem", "One of {pem, der, base64}")
    48  	flag.StringVar(&include, "include", "", "Comma-separated list of lints to include by name")
    49  	flag.StringVar(&exclude, "exclude", "", "Comma-separated list of lints to exclude by name")
    50  
    51  	flag.BoolVar(&prettyprint, "pretty", false, "Pretty-print output")
    52  	flag.Usage = func() {
    53  		fmt.Fprintf(os.Stderr, "Usage: %s [flags] file...\n", os.Args[0])
    54  		flag.PrintDefaults()
    55  	}
    56  	flag.Parse()
    57  	log.SetLevel(log.InfoLevel)
    58  }
    59  
    60  func main() {
    61  
    62  	if listLintsJSON {
    63  		zlint.EncodeLintDescriptionsToJSON(os.Stdout)
    64  		return
    65  	}
    66  
    67  	if listLintsSchema {
    68  		names := make([]string, 0, len(lints.Lints))
    69  		for lintName := range lints.Lints {
    70  			names = append(names, lintName)
    71  		}
    72  		sort.Strings(names)
    73  		fmt.Printf("Lints = SubRecord({\n")
    74  		for _, lintName := range names {
    75  			fmt.Printf("    \"%s\":LintBool(),\n", lintName)
    76  		}
    77  		fmt.Printf("})\n")
    78  		return
    79  	}
    80  
    81  	// include/exclude lints based on flags
    82  	setLints()
    83  
    84  	var inform = strings.ToLower(format)
    85  	if flag.NArg() < 1 || flag.Arg(0) == "-" {
    86  		lint(os.Stdin, inform)
    87  	} else {
    88  		for _, filePath := range flag.Args() {
    89  			var inputFile *os.File
    90  			var err error
    91  			inputFile, err = os.Open(filePath)
    92  			if err != nil {
    93  				log.Fatalf("unable to open file %s: %s", filePath, err)
    94  			}
    95  			var fileInform = inform
    96  			switch {
    97  			case strings.HasSuffix(filePath, ".der"):
    98  				fileInform = "der"
    99  			case strings.HasSuffix(filePath, ".pem"):
   100  				fileInform = "pem"
   101  			}
   102  
   103  			lint(inputFile, fileInform)
   104  			inputFile.Close()
   105  		}
   106  	}
   107  }
   108  
   109  func lint(inputFile *os.File, inform string) {
   110  	fileBytes, err := ioutil.ReadAll(inputFile)
   111  	if err != nil {
   112  		log.Fatalf("unable to read file %s: %s", inputFile.Name(), err)
   113  	}
   114  
   115  	var asn1Data []byte
   116  	switch inform {
   117  	case "pem":
   118  		p, _ := pem.Decode(fileBytes)
   119  		if p == nil || p.Type != "CERTIFICATE" {
   120  			log.Fatal("unable to parse PEM")
   121  		}
   122  		asn1Data = p.Bytes
   123  	case "der":
   124  		asn1Data = fileBytes
   125  	case "base64":
   126  		asn1Data, err = base64.StdEncoding.DecodeString(string(fileBytes))
   127  		if err != nil {
   128  			log.Fatalf("unable to parse base64: %s", err)
   129  		}
   130  	default:
   131  		log.Fatalf("unknown input format %s", format)
   132  	}
   133  
   134  	c, err := x509.ParseCertificate(asn1Data)
   135  	if err != nil {
   136  		log.Fatalf("unable to parse certificate: %s", err)
   137  	}
   138  
   139  	zlintResult := zlint.LintCertificate(c)
   140  	jsonBytes, err := json.Marshal(zlintResult.Results)
   141  	if err != nil {
   142  		log.Fatalf("unable to encode lints JSON: %s", err)
   143  	}
   144  	if prettyprint {
   145  		var out bytes.Buffer
   146  		if err := json.Indent(&out, jsonBytes, "", " "); err != nil {
   147  			log.Fatalf("can't format output: %s", err)
   148  		}
   149  		os.Stdout.Write(out.Bytes())
   150  	} else {
   151  		os.Stdout.Write(jsonBytes)
   152  	}
   153  	os.Stdout.Write([]byte{'\n'})
   154  	os.Stdout.Sync()
   155  }
   156  
   157  func setLints() {
   158  	if include != "" && exclude != "" {
   159  		log.Fatal("unable to use include and exclude flag at the same time")
   160  	}
   161  
   162  	includeLints()
   163  	excludeLints()
   164  }
   165  
   166  func includeLints() {
   167  	if include == "" {
   168  		return
   169  	}
   170  
   171  	// parse includes to map for easier matching
   172  	var includes = strings.Split(include, ",")
   173  	var includesMap = make(map[string]struct{}, len(includes))
   174  	for _, includeName := range includes {
   175  		includeName = strings.TrimSpace(includeName)
   176  		if _, ok := lints.Lints[includeName]; !ok {
   177  			log.Fatalf("unknown lint %q in include list", includeName)
   178  		}
   179  
   180  		includesMap[includeName] = struct{}{}
   181  	}
   182  
   183  	// clear all initialised lints except for includes
   184  	for lintName := range lints.Lints {
   185  		if _, ok := includesMap[lintName]; !ok {
   186  			delete(lints.Lints, lintName)
   187  		}
   188  	}
   189  }
   190  
   191  func excludeLints() {
   192  	if exclude == "" {
   193  		return
   194  	}
   195  
   196  	// parse excludes to map to get rid of duplicates
   197  	var excludes = strings.Split(exclude, ",")
   198  	var excludesMap = make(map[string]struct{}, len(excludes))
   199  	for _, excludeName := range excludes {
   200  		excludesMap[strings.TrimSpace(excludeName)] = struct{}{}
   201  	}
   202  
   203  	// exclude lints
   204  	for excludeName := range excludesMap {
   205  		if _, ok := lints.Lints[excludeName]; !ok {
   206  			log.Fatalf("unknown lint %q in exclude list", excludeName)
   207  		}
   208  
   209  		delete(lints.Lints, excludeName)
   210  	}
   211  }