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