summaryrefslogtreecommitdiffstats
path: root/dependencies/pkg/mod/golang.org/x/exp@v0.0.0-20220613132600-b0d781184e0d/cmd/macos-roots-test/root_darwin.go
blob: b5bf0f777c4dd1b9045f513c3250bec3a6106760 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"bytes"
	"crypto/x509"
	"encoding/pem"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"os/user"
	"path/filepath"
	"sync"
)

var debugDarwinRoots = true

// This code is only used when compiling without cgo.
// It is here, instead of root_nocgo_darwin.go, so that tests can check it
// even if the tests are run with cgo enabled.
// The linker will not include these unused functions in binaries built with cgo enabled.

// execSecurityRoots finds the macOS list of trusted root certificates
// using only command-line tools. This is our fallback path when cgo isn't available.
//
// The strategy is as follows:
//
//  1. Run "security find-certificate" to dump the list of system root
//     CAs in PEM format.
//
//  2. For each dumped cert, conditionally verify it with "security
//     verify-cert" if that cert was not in the SystemRootCertificates
//     keychain, which can't have custom trust policies.
//
// We need to run "verify-cert" for all certificates not in SystemRootCertificates
// because there might be certificates in the keychains without a corresponding
// trust entry, in which case the logic is complicated (see root_cgo_darwin.go).
//
// TODO: actually parse the "trust-settings-export" output and apply the full
// logic. See Issue 26830.
func execSecurityRoots() (*x509.CertPool, error) {
	keychains := []string{"/Library/Keychains/System.keychain"}

	// Note that this results in trusting roots from $HOME/... (the environment
	// variable), which might not be expected.
	u, err := user.Current()
	if err != nil {
		if debugDarwinRoots {
			fmt.Printf("crypto/x509: get current user: %v\n", err)
		}
	} else {
		keychains = append(keychains,
			filepath.Join(u.HomeDir, "/Library/Keychains/login.keychain"),

			// Fresh installs of Sierra use a slightly different path for the login keychain
			filepath.Join(u.HomeDir, "/Library/Keychains/login.keychain-db"),
		)
	}

	var (
		mu          sync.Mutex
		roots       = x509.NewCertPool()
		numVerified int // number of execs of 'security verify-cert', for debug stats
		wg          sync.WaitGroup
		verifyCh    = make(chan *x509.Certificate)
	)

	// Using 4 goroutines to pipe into verify-cert seems to be
	// about the best we can do. The verify-cert binary seems to
	// just RPC to another server with coarse locking anyway, so
	// running 16 at a time for instance doesn't help at all.
	for i := 0; i < 4; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			for cert := range verifyCh {
				valid := verifyCertWithSystem(cert)

				mu.Lock()
				numVerified++
				if valid {
					roots.AddCert(cert)
				}
				mu.Unlock()
			}
		}()
	}
	err = forEachCertInKeychains(keychains, func(cert *x509.Certificate) {
		verifyCh <- cert
	})
	if err != nil {
		return nil, err
	}
	close(verifyCh)
	wg.Wait()

	if debugDarwinRoots {
		fmt.Printf("crypto/x509: ran security verify-cert %d times\n", numVerified)
	}

	err = forEachCertInKeychains([]string{
		"/System/Library/Keychains/SystemRootCertificates.keychain",
	}, roots.AddCert)
	if err != nil {
		return nil, err
	}

	return roots, nil
}

func forEachCertInKeychains(paths []string, f func(*x509.Certificate)) error {
	args := append([]string{"find-certificate", "-a", "-p"}, paths...)
	cmd := exec.Command("/usr/bin/security", args...)
	data, err := cmd.Output()
	if err != nil {
		return err
	}
	for len(data) > 0 {
		var block *pem.Block
		block, data = pem.Decode(data)
		if block == nil {
			break
		}
		if block.Type != "CERTIFICATE" || len(block.Headers) != 0 {
			continue
		}
		cert, err := x509.ParseCertificate(block.Bytes)
		if err != nil {
			continue
		}
		f(cert)
	}
	return nil
}

func verifyCertWithSystem(cert *x509.Certificate) bool {
	data := pem.EncodeToMemory(&pem.Block{
		Type: "CERTIFICATE", Bytes: cert.Raw,
	})

	f, err := ioutil.TempFile("", "cert")
	if err != nil {
		fmt.Fprintf(os.Stderr, "can't create temporary file for cert: %v", err)
		return false
	}
	defer os.Remove(f.Name())
	if _, err := f.Write(data); err != nil {
		fmt.Fprintf(os.Stderr, "can't write temporary file for cert: %v", err)
		return false
	}
	if err := f.Close(); err != nil {
		fmt.Fprintf(os.Stderr, "can't write temporary file for cert: %v", err)
		return false
	}
	cmd := exec.Command("/usr/bin/security", "verify-cert", "-p", "ssl", "-c", f.Name(), "-l", "-L")
	var stderr bytes.Buffer
	if debugDarwinRoots {
		cmd.Stderr = &stderr
	}
	if err := cmd.Run(); err != nil {
		if debugDarwinRoots {
			fmt.Printf("crypto/x509: verify-cert rejected %s: %q\n", cert.Subject, bytes.TrimSpace(stderr.Bytes()))
		}
		return false
	}
	if debugDarwinRoots {
		fmt.Printf("crypto/x509: verify-cert approved %s\n", cert.Subject)
	}
	return true
}