// 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 }