github.com/FISCO-BCOS/crypto@v0.0.0-20200202032121-bd8ab0b5d4f1/x509/root_darwin.go (about) 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 7 package x509 8 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 "path/filepath" 20 "strings" 21 "sync" 22 ) 23 24 var debugDarwinRoots = strings.Contains(os.Getenv("GODEBUG"), "x509roots=1") 25 26 func (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) 55 func execSecurityRoots() (*CertPool, error) { 56 hasPolicy, err := getCertsWithTrustPolicy() 57 if err != nil { 58 return nil, err 59 } 60 if debugDarwinRoots { 61 fmt.Fprintf(os.Stderr, "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.Fprintf(os.Stderr, "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.Fprintf(os.Stderr, "crypto/x509: ran security verify-cert %d times\n", numVerified) 152 } 153 154 return roots, nil 155 } 156 157 func 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 182 func 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.Fprintf(os.Stderr, "crypto/x509: verify-cert rejected %s: %q\n", cert.Subject, bytes.TrimSpace(stderr.Bytes())) 209 } 210 return false 211 } 212 if debugDarwinRoots { 213 fmt.Fprintf(os.Stderr, "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. 224 func 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.Fprintf(os.Stderr, "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 }