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
|
// 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"
"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 := os.CreateTemp("", "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
}
|