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 15package x509util 16 17import ( 18 "encoding/pem" 19 "fmt" 20 "io/ioutil" 21 "net/http" 22 "net/url" 23 "strings" 24 25 "github.com/google/certificate-transparency-go/x509" 26) 27 28// ReadPossiblePEMFile loads data from a file which may be in DER format 29// or may be in PEM format (with the given blockname). 30func ReadPossiblePEMFile(filename, blockname string) ([][]byte, error) { 31 data, err := ioutil.ReadFile(filename) 32 if err != nil { 33 return nil, fmt.Errorf("%s: failed to read data: %v", filename, err) 34 } 35 return dePEM(data, blockname), nil 36} 37 38// ReadPossiblePEMURL attempts to determine if the given target is a local file or a 39// URL, and return the file contents regardless. It also copes with either PEM or DER 40// format data. 41func ReadPossiblePEMURL(target, blockname string) ([][]byte, error) { 42 if !strings.HasPrefix(target, "http://") && !strings.HasPrefix(target, "https://") { 43 // Assume it's a filename 44 return ReadPossiblePEMFile(target, blockname) 45 } 46 47 rsp, err := http.Get(target) 48 if err != nil { 49 return nil, fmt.Errorf("failed to http.Get(%q): %v", target, err) 50 } 51 data, err := ioutil.ReadAll(rsp.Body) 52 if err != nil { 53 return nil, fmt.Errorf("failed to ioutil.ReadAll(%q): %v", target, err) 54 } 55 return dePEM(data, blockname), nil 56} 57 58func dePEM(data []byte, blockname string) [][]byte { 59 var results [][]byte 60 if strings.Contains(string(data), "BEGIN "+blockname) { 61 rest := data 62 for { 63 var block *pem.Block 64 block, rest = pem.Decode(rest) 65 if block == nil { 66 break 67 } 68 if block.Type == blockname { 69 results = append(results, block.Bytes) 70 } 71 } 72 } else { 73 results = append(results, data) 74 } 75 return results 76} 77 78// ReadFileOrURL returns the data from a target which may be either a filename 79// or an HTTP(S) URL. 80func ReadFileOrURL(target string, client *http.Client) ([]byte, error) { 81 u, err := url.Parse(target) 82 if err != nil || (u.Scheme != "http" && u.Scheme != "https") { 83 return ioutil.ReadFile(target) 84 } 85 86 rsp, err := client.Get(u.String()) 87 if err != nil { 88 return nil, fmt.Errorf("failed to http.Get(%q): %v", target, err) 89 } 90 return ioutil.ReadAll(rsp.Body) 91} 92 93// GetIssuer attempts to retrieve the issuer for a certificate, by examining 94// the cert's Authority Information Access extension (if present) for the 95// issuer's URL and retrieving from there. 96func GetIssuer(cert *x509.Certificate, client *http.Client) (*x509.Certificate, error) { 97 if len(cert.IssuingCertificateURL) == 0 { 98 return nil, nil 99 } 100 issuerURL := cert.IssuingCertificateURL[0] 101 rsp, err := client.Get(issuerURL) 102 if err != nil || rsp.StatusCode != http.StatusOK { 103 return nil, fmt.Errorf("failed to get issuer from %q: %v", issuerURL, err) 104 } 105 defer rsp.Body.Close() 106 body, err := ioutil.ReadAll(rsp.Body) 107 if err != nil { 108 return nil, fmt.Errorf("failed to read issuer from %q: %v", issuerURL, err) 109 } 110 issuers, err := x509.ParseCertificates(body) 111 if err != nil { 112 return nil, fmt.Errorf("failed to parse issuer cert: %v", err) 113 } 114 return issuers[0], nil 115} 116