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
25 var debugExecDarwinRoots
= strings
.Contains(os
.Getenv("GODEBUG"), "x509roots=1")
27 func (c
*Certificate
) systemVerify(opts
*VerifyOptions
) (chains
[][]*Certificate
, err error
) {
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
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()
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()
72 if debugExecDarwinRoots
{
73 println(fmt
.Sprintf("crypto/x509: get current user: %v", err
))
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()
93 numVerified
int // number of execs of 'security verify-cert', for debug stats
96 blockCh
:= make(chan *pem
.Block
)
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
++ {
112 for block
:= range blockCh
{
113 cert
, err
:= ParseCertificate(block
.Bytes
)
117 sha1CapHex
:= fmt
.Sprintf("%X", sha1
.Sum(block
.Bytes
))
121 if hasPolicy
[sha1CapHex
] {
123 if !verifyCertWithSystem(block
, cert
) {
129 numVerified
+= verifyChecks
139 block
, data
= pem
.Decode(data
)
143 if block
.Type
!= "CERTIFICATE" ||
len(block
.Headers
) != 0 {
151 if debugExecDarwinRoots
{
154 println(fmt
.Sprintf("crypto/x509: ran security verify-cert %d times", numVerified
))
160 func verifyCertWithSystem(block
*pem
.Block
, cert
*Certificate
) bool {
161 data
:= pem
.EncodeToMemory(block
)
163 f
, err
:= ioutil
.TempFile("", "cert")
165 fmt
.Fprintf(os
.Stderr
, "can't create temporary file for cert: %v", err
)
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
)
173 if err
:= f
.Close(); err
!= nil {
174 fmt
.Fprintf(os
.Stderr
, "can't write temporary file for cert: %v", err
)
177 cmd
:= exec
.Command("/usr/bin/security", "verify-cert", "-c", f
.Name(), "-l", "-L")
178 var stderr bytes
.Buffer
179 if debugExecDarwinRoots
{
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())))
188 if debugExecDarwinRoots
{
189 println(fmt
.Sprintf("crypto/x509: verify-cert approved %s", cert
.Subject
))
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")
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
213 if err
:= cmd
.Run(); err
!= nil {
214 // If there are no trust settings, the
215 // `security trust-settings-export` command
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()))
227 f
, err
:= os
.Open(file
)
233 // Gather all the runs of 40 capitalized hex characters.
234 br
:= bufio
.NewReader(f
)
235 var hexBuf bytes
.Buffer
237 b
, err
:= br
.ReadByte()
238 isHex
:= ('A' <= b
&& b
<= 'F') ||
('0' <= b
&& b
<= '9')
242 if hexBuf
.Len() == 40 {
243 set
[hexBuf
.String()] = true
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
)