1// Copyright 2015 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// +build ignore
6
7// Generates root_darwin_armx.go.
8//
9// As of iOS 8, there is no API for querying the system trusted X.509 root
10// certificates. We could use SecTrustEvaluate to verify that a trust chain
11// exists for a certificate, but the x509 API requires returning the entire
12// chain.
13//
14// Apple publishes the list of trusted root certificates for iOS on
15// support.apple.com. So we parse the list and extract the certificates from
16// an OS X machine and embed them into the x509 package.
17package main
18
19import (
20	"bytes"
21	"crypto/sha256"
22	"crypto/x509"
23	"encoding/hex"
24	"encoding/pem"
25	"flag"
26	"fmt"
27	"go/format"
28	"io/ioutil"
29	"log"
30	"net/http"
31	"os/exec"
32	"regexp"
33	"strings"
34)
35
36var output = flag.String("output", "root_darwin_armx.go", "file name to write")
37
38func main() {
39	certs, err := selectCerts()
40	if err != nil {
41		log.Fatal(err)
42	}
43
44	buf := new(bytes.Buffer)
45
46	fmt.Fprintf(buf, "// Code generated by root_darwin_arm_gen --output %s; DO NOT EDIT.\n", *output)
47	fmt.Fprintf(buf, "%s", header)
48
49	fmt.Fprintf(buf, "const systemRootsPEM = `\n")
50	for _, cert := range certs {
51		b := &pem.Block{
52			Type:  "CERTIFICATE",
53			Bytes: cert.Raw,
54		}
55		if err := pem.Encode(buf, b); err != nil {
56			log.Fatal(err)
57		}
58	}
59	fmt.Fprintf(buf, "`")
60
61	source, err := format.Source(buf.Bytes())
62	if err != nil {
63		log.Fatal("source format error:", err)
64	}
65	if err := ioutil.WriteFile(*output, source, 0644); err != nil {
66		log.Fatal(err)
67	}
68}
69
70func selectCerts() ([]*x509.Certificate, error) {
71	ids, err := fetchCertIDs()
72	if err != nil {
73		return nil, err
74	}
75
76	scerts, err := sysCerts()
77	if err != nil {
78		return nil, err
79	}
80
81	var certs []*x509.Certificate
82	for _, id := range ids {
83		if c, ok := scerts[id.fingerprint]; ok {
84			certs = append(certs, c)
85		} else {
86			fmt.Printf("WARNING: cannot find certificate: %s (fingerprint: %s)\n", id.name, id.fingerprint)
87		}
88	}
89	return certs, nil
90}
91
92func sysCerts() (certs map[string]*x509.Certificate, err error) {
93	cmd := exec.Command("/usr/bin/security", "find-certificate", "-a", "-p", "/System/Library/Keychains/SystemRootCertificates.keychain")
94	data, err := cmd.Output()
95	if err != nil {
96		return nil, err
97	}
98	certs = make(map[string]*x509.Certificate)
99	for len(data) > 0 {
100		var block *pem.Block
101		block, data = pem.Decode(data)
102		if block == nil {
103			break
104		}
105		if block.Type != "CERTIFICATE" || len(block.Headers) != 0 {
106			continue
107		}
108
109		cert, err := x509.ParseCertificate(block.Bytes)
110		if err != nil {
111			continue
112		}
113
114		fingerprint := sha256.Sum256(cert.Raw)
115		certs[hex.EncodeToString(fingerprint[:])] = cert
116	}
117	return certs, nil
118}
119
120type certID struct {
121	name        string
122	fingerprint string
123}
124
125// fetchCertIDs fetches IDs of iOS X509 certificates from apple.com.
126func fetchCertIDs() ([]certID, error) {
127	// Download the iOS 11 support page. The index for all iOS versions is here:
128	// https://support.apple.com/en-us/HT204132
129	resp, err := http.Get("https://support.apple.com/en-us/HT208125")
130	if err != nil {
131		return nil, err
132	}
133	defer resp.Body.Close()
134	body, err := ioutil.ReadAll(resp.Body)
135	if err != nil {
136		return nil, err
137	}
138	text := string(body)
139	text = text[strings.Index(text, "<div id=trusted"):]
140	text = text[:strings.Index(text, "</div>")]
141
142	var ids []certID
143	cols := make(map[string]int)
144	for i, rowmatch := range regexp.MustCompile("(?s)<tr>(.*?)</tr>").FindAllStringSubmatch(text, -1) {
145		row := rowmatch[1]
146		if i == 0 {
147			// Parse table header row to extract column names
148			for i, match := range regexp.MustCompile("(?s)<th>(.*?)</th>").FindAllStringSubmatch(row, -1) {
149				cols[match[1]] = i
150			}
151			continue
152		}
153
154		values := regexp.MustCompile("(?s)<td>(.*?)</td>").FindAllStringSubmatch(row, -1)
155		name := values[cols["Certificate name"]][1]
156		fingerprint := values[cols["Fingerprint (SHA-256)"]][1]
157		fingerprint = strings.ReplaceAll(fingerprint, "<br>", "")
158		fingerprint = strings.ReplaceAll(fingerprint, "\n", "")
159		fingerprint = strings.ReplaceAll(fingerprint, " ", "")
160		fingerprint = strings.ToLower(fingerprint)
161
162		ids = append(ids, certID{
163			name:        name,
164			fingerprint: fingerprint,
165		})
166	}
167	return ids, nil
168}
169
170const header = `
171// Copyright 2015 The Go Authors. All rights reserved.
172// Use of this source code is governed by a BSD-style
173// license that can be found in the LICENSE file.
174
175// +build cgo
176// +build darwin
177// +build arm arm64 ios
178
179package x509
180
181func loadSystemRoots() (*CertPool, error) {
182	p := NewCertPool()
183	p.AppendCertsFromPEM([]byte(systemRootsPEM))
184	return p, nil
185}
186`
187