github.com/zmap/zcrypto@v0.0.0-20240512203510-0fef58d9a9db/x509/zintermediate/zintermediate.go (about) 1 /* 2 * ZCrypto 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 // ZIntermediate is a command line utility for verifying a set prospective 16 // intermediate certificates against a root store. Given a set of root 17 // certificates in PEM format, it can then read in a list of candidate 18 // intermediates. Candidate certificates are verified against the root store, 19 // and can optionally chain through any other candidate. All candidate 20 // certificates will be stored in memory during validation. 21 // 22 // ZIntermediate returns any candidate certificate with a chain back to the root 23 // store, and ignores date-related errors and extended key usage flags, meaning 24 // ZIntermediate will return both expired intermediates and code-signing 25 // certificates. 26 // 27 // While the candidate certificates can be any certificate, ZIntermediate 28 // expects they will be intermediates. If a non-intermediate certificate (e.g. a 29 // certificate without IsCA set to true) is input, ZIntermediate will not build 30 // chains through it, but will output it as valid. 31 // 32 // Examples: 33 // 34 // $ zintermediate --roots roots.pem candidates.csv > intermediates.pem 35 package main 36 37 import ( 38 "bufio" 39 "encoding/base64" 40 "encoding/pem" 41 "errors" 42 "flag" 43 "fmt" 44 "io" 45 "os" 46 "strings" 47 "time" 48 49 "github.com/zmap/zcrypto/x509" 50 51 "github.com/op/go-logging" 52 ) 53 54 var log = logging.MustGetLogger("") 55 56 var inputFormatArg string 57 58 type inputFormatType int 59 60 const ( 61 inputFormatBase64 inputFormatType = iota 62 inputFormatPEM 63 inputFormatJSON 64 ) 65 66 var inputFormat inputFormatType 67 var inputFileName, rootFileName string 68 69 func init() { 70 flag.StringVar(&inputFormatArg, "format", "base64", "One of {base64, pem, json}") 71 flag.StringVar(&rootFileName, "roots", "roots.pem", "Path to root store") 72 flag.Parse() 73 74 if flag.NArg() < 1 { 75 log.Fatalf("missing filename") 76 } 77 inputFileName = flag.Arg(0) 78 79 inputFormatArg = strings.ToLower(inputFormatArg) 80 switch inputFormatArg { 81 case "base64": 82 inputFormat = inputFormatBase64 83 case "pem": 84 inputFormat = inputFormatPEM 85 case "json": 86 inputFormat = inputFormatJSON 87 default: 88 log.Fatalf("unknown argument for --format \"%s\", see --help", inputFormatArg) 89 } 90 } 91 92 func loadPEMPool(r io.Reader) (*x509.CertPool, error) { 93 out := x509.NewCertPool() 94 scanner := bufio.NewScanner(r) 95 96 scanner.Split(func(data []byte, atEOF bool) (int, []byte, error) { 97 block, rest := pem.Decode(data) 98 if block != nil { 99 size := len(data) - len(rest) 100 return size, data[:size], nil 101 } 102 return 0, nil, nil 103 }) 104 105 for scanner.Scan() { 106 pemBytes := scanner.Bytes() 107 ok := out.AppendCertsFromPEM(pemBytes) 108 if !ok { 109 log.Errorf("could not load PEM: %s", scanner.Text()) 110 return nil, errors.New("unable to load PEM") 111 } 112 } 113 return out, nil 114 } 115 116 func loadBase64Pool(r io.Reader) (*x509.CertPool, error) { 117 out := x509.NewCertPool() 118 scanner := bufio.NewScanner(r) 119 scanner.Buffer(nil, 1024*1024*10) 120 var lines int 121 for lines = 0; scanner.Scan(); lines += 1 { 122 line := scanner.Text() 123 raw, err := base64.StdEncoding.DecodeString(line) 124 if err != nil { 125 log.Errorf("could not read base64: %s", line) 126 return nil, err 127 } 128 c, err := x509.ParseCertificate(raw) 129 if err != nil { 130 log.Errorf("could not read certificate %s: %s", line, err) 131 continue 132 } 133 out.AddCert(c) 134 } 135 if err := scanner.Err(); err != nil { 136 log.Fatalf("%s", err) 137 } 138 log.Infof("read %d lines", lines) 139 return out, nil 140 } 141 142 func main() { 143 log.Infof("loading roots from %s", rootFileName) 144 rootFile, err := os.Open(rootFileName) 145 if err != nil { 146 log.Fatalf("could not open %s: %s", rootFileName, err) 147 } 148 rootPool, err := loadPEMPool(rootFile) 149 rootFile.Close() 150 if err != nil { 151 log.Fatalf("could not load roots: %s", err) 152 } 153 154 log.Infof("loading candidate intermediates from %s", inputFileName) 155 intermediateFile, err := os.Open(inputFileName) 156 if err != nil { 157 log.Fatalf("could not open %s: %s", inputFileName, err) 158 } 159 log.Infof("using input format type %s", inputFormatArg) 160 var candidatePool *x509.CertPool 161 switch inputFormat { 162 case inputFormatPEM: 163 candidatePool, err = loadPEMPool(intermediateFile) 164 case inputFormatBase64: 165 candidatePool, err = loadBase64Pool(intermediateFile) 166 default: 167 err = fmt.Errorf("unimplemented input type: %s", inputFormatArg) 168 } 169 intermediateFile.Close() 170 171 if err != nil { 172 log.Fatalf("could not load candidate intermediates: %s", err) 173 } 174 candidates := candidatePool.Certificates() 175 log.Infof("loaded %d candidates", len(candidates)) 176 177 now := time.Now() 178 verifyOpts := x509.VerifyOptions{ 179 Roots: rootPool, 180 Intermediates: candidatePool, 181 CurrentTime: now, 182 KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, 183 } 184 185 log.Infof("validating candidates") 186 intermediates := make([]*x509.Certificate, 0) 187 rejected := 0 188 for idx, candidate := range candidates { 189 if idx > 0 && idx%1000 == 0 { 190 log.Infof("checked %d candidates", idx) 191 } 192 if current, expired, never, _ := candidate.Verify(verifyOpts); len(current) > 0 || len(expired) > 0 || len(never) > 0 { 193 intermediates = append(intermediates, candidate) 194 continue 195 } 196 rejected++ 197 } 198 log.Infof("validation complete") 199 log.Infof("found %d intermediates", len(intermediates)) 200 log.Infof("rejected %d candidates", rejected) 201 202 log.Infof("outputing intermediates") 203 for _, c := range intermediates { 204 block := pem.Block{ 205 Type: "CERTIFICATE", 206 Bytes: c.Raw, 207 } 208 pem.Encode(os.Stdout, &block) 209 } 210 log.Infof("complete") 211 }