1// Copyright 2013 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//go:generate go run root_darwin_arm_gen.go -output root_darwin_armx.go
6
7package x509
8
9import (
10	"bufio"
11	"bytes"
12	"crypto/sha1"
13	"encoding/pem"
14	"fmt"
15	"io"
16	"io/ioutil"
17	"os"
18	"os/exec"
19	"path/filepath"
20	"strings"
21	"sync"
22)
23
24var debugDarwinRoots = strings.Contains(os.Getenv("GODEBUG"), "x509roots=1")
25
26func (c *Certificate) systemVerify(opts *VerifyOptions) (chains [][]*Certificate, err error) {
27	return nil, nil
28}
29
30// This code is only used when compiling without cgo.
31// It is here, instead of root_nocgo_darwin.go, so that tests can check it
32// even if the tests are run with cgo enabled.
33// The linker will not include these unused functions in binaries built with cgo enabled.
34
35// execSecurityRoots finds the macOS list of trusted root certificates
36// using only command-line tools. This is our fallback path when cgo isn't available.
37//
38// The strategy is as follows:
39//
40// 1. Run "security trust-settings-export" and "security
41//    trust-settings-export -d" to discover the set of certs with some
42//    user-tweaked trust policy. We're too lazy to parse the XML
43//    (Issue 26830) to understand what the trust
44//    policy actually is. We just learn that there is _some_ policy.
45//
46// 2. Run "security find-certificate" to dump the list of system root
47//    CAs in PEM format.
48//
49// 3. For each dumped cert, conditionally verify it with "security
50//    verify-cert" if that cert was in the set discovered in Step 1.
51//    Without the Step 1 optimization, running "security verify-cert"
52//    150-200 times takes 3.5 seconds. With the optimization, the
53//    whole process takes about 180 milliseconds with 1 untrusted root
54//    CA. (Compared to 110ms in the cgo path)
55func execSecurityRoots() (*CertPool, error) {
56	hasPolicy, err := getCertsWithTrustPolicy()
57	if err != nil {
58		return nil, err
59	}
60	if debugDarwinRoots {
61		fmt.Printf("crypto/x509: %d certs have a trust policy\n", len(hasPolicy))
62	}
63
64	keychains := []string{"/Library/Keychains/System.keychain"}
65
66	// Note that this results in trusting roots from $HOME/... (the environment
67	// variable), which might not be expected.
68	home, err := os.UserHomeDir()
69	if err != nil {
70		if debugDarwinRoots {
71			fmt.Printf("crypto/x509: can't get user home directory: %v\n", err)
72		}
73	} else {
74		keychains = append(keychains,
75			filepath.Join(home, "/Library/Keychains/login.keychain"),
76
77			// Fresh installs of Sierra use a slightly different path for the login keychain
78			filepath.Join(home, "/Library/Keychains/login.keychain-db"),
79		)
80	}
81
82	type rootCandidate struct {
83		c      *Certificate
84		system bool
85	}
86
87	var (
88		mu          sync.Mutex
89		roots       = NewCertPool()
90		numVerified int // number of execs of 'security verify-cert', for debug stats
91		wg          sync.WaitGroup
92		verifyCh    = make(chan rootCandidate)
93	)
94
95	// Using 4 goroutines to pipe into verify-cert seems to be
96	// about the best we can do. The verify-cert binary seems to
97	// just RPC to another server with coarse locking anyway, so
98	// running 16 at a time for instance doesn't help at all. Due
99	// to the "if hasPolicy" check below, though, we will rarely
100	// (or never) call verify-cert on stock macOS systems, though.
101	// The hope is that we only call verify-cert when the user has
102	// tweaked their trust policy. These 4 goroutines are only
103	// defensive in the pathological case of many trust edits.
104	for i := 0; i < 4; i++ {
105		wg.Add(1)
106		go func() {
107			defer wg.Done()
108			for cert := range verifyCh {
109				sha1CapHex := fmt.Sprintf("%X", sha1.Sum(cert.c.Raw))
110
111				var valid bool
112				verifyChecks := 0
113				if hasPolicy[sha1CapHex] {
114					verifyChecks++
115					valid = verifyCertWithSystem(cert.c)
116				} else {
117					// Certificates not in SystemRootCertificates without user
118					// or admin trust settings are not trusted.
119					valid = cert.system
120				}
121
122				mu.Lock()
123				numVerified += verifyChecks
124				if valid {
125					roots.AddCert(cert.c)
126				}
127				mu.Unlock()
128			}
129		}()
130	}
131	err = forEachCertInKeychains(keychains, func(cert *Certificate) {
132		verifyCh <- rootCandidate{c: cert, system: false}
133	})
134	if err != nil {
135		close(verifyCh)
136		return nil, err
137	}
138	err = forEachCertInKeychains([]string{
139		"/System/Library/Keychains/SystemRootCertificates.keychain",
140	}, func(cert *Certificate) {
141		verifyCh <- rootCandidate{c: cert, system: true}
142	})
143	if err != nil {
144		close(verifyCh)
145		return nil, err
146	}
147	close(verifyCh)
148	wg.Wait()
149
150	if debugDarwinRoots {
151		fmt.Printf("crypto/x509: ran security verify-cert %d times\n", numVerified)
152	}
153
154	return roots, nil
155}
156
157func forEachCertInKeychains(paths []string, f func(*Certificate)) error {
158	args := append([]string{"find-certificate", "-a", "-p"}, paths...)
159	cmd := exec.Command("/usr/bin/security", args...)
160	data, err := cmd.Output()
161	if err != nil {
162		return err
163	}
164	for len(data) > 0 {
165		var block *pem.Block
166		block, data = pem.Decode(data)
167		if block == nil {
168			break
169		}
170		if block.Type != "CERTIFICATE" || len(block.Headers) != 0 {
171			continue
172		}
173		cert, err := ParseCertificate(block.Bytes)
174		if err != nil {
175			continue
176		}
177		f(cert)
178	}
179	return nil
180}
181
182func verifyCertWithSystem(cert *Certificate) bool {
183	data := pem.EncodeToMemory(&pem.Block{
184		Type: "CERTIFICATE", Bytes: cert.Raw,
185	})
186
187	f, err := ioutil.TempFile("", "cert")
188	if err != nil {
189		fmt.Fprintf(os.Stderr, "can't create temporary file for cert: %v", err)
190		return false
191	}
192	defer os.Remove(f.Name())
193	if _, err := f.Write(data); err != nil {
194		fmt.Fprintf(os.Stderr, "can't write temporary file for cert: %v", err)
195		return false
196	}
197	if err := f.Close(); err != nil {
198		fmt.Fprintf(os.Stderr, "can't write temporary file for cert: %v", err)
199		return false
200	}
201	cmd := exec.Command("/usr/bin/security", "verify-cert", "-p", "ssl", "-c", f.Name(), "-l", "-L")
202	var stderr bytes.Buffer
203	if debugDarwinRoots {
204		cmd.Stderr = &stderr
205	}
206	if err := cmd.Run(); err != nil {
207		if debugDarwinRoots {
208			fmt.Printf("crypto/x509: verify-cert rejected %s: %q\n", cert.Subject, bytes.TrimSpace(stderr.Bytes()))
209		}
210		return false
211	}
212	if debugDarwinRoots {
213		fmt.Printf("crypto/x509: verify-cert approved %s\n", cert.Subject)
214	}
215	return true
216}
217
218// getCertsWithTrustPolicy returns the set of certs that have a
219// possibly-altered trust policy. The keys of the map are capitalized
220// sha1 hex of the raw cert.
221// They are the certs that should be checked against `security
222// verify-cert` to see whether the user altered the default trust
223// settings. This code is only used for cgo-disabled builds.
224func getCertsWithTrustPolicy() (map[string]bool, error) {
225	set := map[string]bool{}
226	td, err := ioutil.TempDir("", "x509trustpolicy")
227	if err != nil {
228		return nil, err
229	}
230	defer os.RemoveAll(td)
231	run := func(file string, args ...string) error {
232		file = filepath.Join(td, file)
233		args = append(args, file)
234		cmd := exec.Command("/usr/bin/security", args...)
235		var stderr bytes.Buffer
236		cmd.Stderr = &stderr
237		if err := cmd.Run(); err != nil {
238			// If there are no trust settings, the
239			// `security trust-settings-export` command
240			// fails with:
241			//    exit status 1, SecTrustSettingsCreateExternalRepresentation: No Trust Settings were found.
242			// Rather than match on English substrings that are probably
243			// localized on macOS, just interpret any failure to mean that
244			// there are no trust settings.
245			if debugDarwinRoots {
246				fmt.Printf("crypto/x509: exec %q: %v, %s\n", cmd.Args, err, stderr.Bytes())
247			}
248			return nil
249		}
250
251		f, err := os.Open(file)
252		if err != nil {
253			return err
254		}
255		defer f.Close()
256
257		// Gather all the runs of 40 capitalized hex characters.
258		br := bufio.NewReader(f)
259		var hexBuf bytes.Buffer
260		for {
261			b, err := br.ReadByte()
262			isHex := ('A' <= b && b <= 'F') || ('0' <= b && b <= '9')
263			if isHex {
264				hexBuf.WriteByte(b)
265			} else {
266				if hexBuf.Len() == 40 {
267					set[hexBuf.String()] = true
268				}
269				hexBuf.Reset()
270			}
271			if err == io.EOF {
272				break
273			}
274			if err != nil {
275				return err
276			}
277		}
278
279		return nil
280	}
281	if err := run("user", "trust-settings-export"); err != nil {
282		return nil, fmt.Errorf("dump-trust-settings (user): %v", err)
283	}
284	if err := run("admin", "trust-settings-export", "-d"); err != nil {
285		return nil, fmt.Errorf("dump-trust-settings (admin): %v", err)
286	}
287	return set, nil
288}
289