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