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 }