123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265 |
- // 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.
- //go:generate go run root_darwin_arm_gen.go -output root_darwin_armx.go
- package x509
- import (
- "bufio"
- "bytes"
- "crypto/sha1"
- "encoding/pem"
- "fmt"
- "io"
- "io/ioutil"
- "os"
- "os/exec"
- "os/user"
- "path/filepath"
- "strings"
- "sync"
- )
- var debugExecDarwinRoots = strings.Contains(os.Getenv("GODEBUG"), "x509roots=1")
- func (c *Certificate) systemVerify(opts *VerifyOptions) (chains [][]*Certificate, err error) {
- return nil, nil
- }
- // 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 trust-settings-export" and "security
- // trust-settings-export -d" to discover the set of certs with some
- // user-tweaked trust policy. We're too lazy to parse the XML (at
- // least at this stage of Go 1.8) to understand what the trust
- // policy actually is. We just learn that there is _some_ policy.
- //
- // 2. Run "security find-certificate" to dump the list of system root
- // CAs in PEM format.
- //
- // 3. For each dumped cert, conditionally verify it with "security
- // verify-cert" if that cert was in the set discovered in Step 1.
- // Without the Step 1 optimization, running "security verify-cert"
- // 150-200 times takes 3.5 seconds. With the optimization, the
- // whole process takes about 180 milliseconds with 1 untrusted root
- // CA. (Compared to 110ms in the cgo path)
- func execSecurityRoots() (*CertPool, error) {
- hasPolicy, err := getCertsWithTrustPolicy()
- if err != nil {
- return nil, err
- }
- if debugExecDarwinRoots {
- println(fmt.Sprintf("crypto/x509: %d certs have a trust policy", len(hasPolicy)))
- }
- args := []string{"find-certificate", "-a", "-p",
- "/System/Library/Keychains/SystemRootCertificates.keychain",
- "/Library/Keychains/System.keychain",
- }
- u, err := user.Current()
- if err != nil {
- if debugExecDarwinRoots {
- println(fmt.Sprintf("crypto/x509: get current user: %v", err))
- }
- } else {
- args = append(args,
- 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"),
- )
- }
- cmd := exec.Command("/usr/bin/security", args...)
- data, err := cmd.Output()
- if err != nil {
- return nil, err
- }
- var (
- mu sync.Mutex
- roots = NewCertPool()
- numVerified int // number of execs of 'security verify-cert', for debug stats
- )
- blockCh := make(chan *pem.Block)
- var wg sync.WaitGroup
- // 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. Due
- // to the "if hasPolicy" check below, though, we will rarely
- // (or never) call verify-cert on stock macOS systems, though.
- // The hope is that we only call verify-cert when the user has
- // tweaked their trust policy. These 4 goroutines are only
- // defensive in the pathological case of many trust edits.
- for i := 0; i < 4; i++ {
- wg.Add(1)
- go func() {
- defer wg.Done()
- for block := range blockCh {
- cert, err := ParseCertificate(block.Bytes)
- if err != nil {
- continue
- }
- sha1CapHex := fmt.Sprintf("%X", sha1.Sum(block.Bytes))
- valid := true
- verifyChecks := 0
- if hasPolicy[sha1CapHex] {
- verifyChecks++
- if !verifyCertWithSystem(block, cert) {
- valid = false
- }
- }
- mu.Lock()
- numVerified += verifyChecks
- if valid {
- roots.AddCert(cert)
- }
- mu.Unlock()
- }
- }()
- }
- 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
- }
- blockCh <- block
- }
- close(blockCh)
- wg.Wait()
- if debugExecDarwinRoots {
- mu.Lock()
- defer mu.Unlock()
- println(fmt.Sprintf("crypto/x509: ran security verify-cert %d times", numVerified))
- }
- return roots, nil
- }
- func verifyCertWithSystem(block *pem.Block, cert *Certificate) bool {
- data := pem.EncodeToMemory(block)
- 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", "-c", f.Name(), "-l", "-L")
- var stderr bytes.Buffer
- if debugExecDarwinRoots {
- cmd.Stderr = &stderr
- }
- if err := cmd.Run(); err != nil {
- if debugExecDarwinRoots {
- println(fmt.Sprintf("crypto/x509: verify-cert rejected %s: %q", cert.Subject.CommonName, bytes.TrimSpace(stderr.Bytes())))
- }
- return false
- }
- if debugExecDarwinRoots {
- println(fmt.Sprintf("crypto/x509: verify-cert approved %s", cert.Subject.CommonName))
- }
- return true
- }
- // getCertsWithTrustPolicy returns the set of certs that have a
- // possibly-altered trust policy. The keys of the map are capitalized
- // sha1 hex of the raw cert.
- // They are the certs that should be checked against `security
- // verify-cert` to see whether the user altered the default trust
- // settings. This code is only used for cgo-disabled builds.
- func getCertsWithTrustPolicy() (map[string]bool, error) {
- set := map[string]bool{}
- td, err := ioutil.TempDir("", "x509trustpolicy")
- if err != nil {
- return nil, err
- }
- defer os.RemoveAll(td)
- run := func(file string, args ...string) error {
- file = filepath.Join(td, file)
- args = append(args, file)
- cmd := exec.Command("/usr/bin/security", args...)
- var stderr bytes.Buffer
- cmd.Stderr = &stderr
- if err := cmd.Run(); err != nil {
- // If there are no trust settings, the
- // `security trust-settings-export` command
- // fails with:
- // exit status 1, SecTrustSettingsCreateExternalRepresentation: No Trust Settings were found.
- // Rather than match on English substrings that are probably
- // localized on macOS, just interpret any failure to mean that
- // there are no trust settings.
- if debugExecDarwinRoots {
- println(fmt.Sprintf("crypto/x509: exec %q: %v, %s", cmd.Args, err, stderr.Bytes()))
- }
- return nil
- }
- f, err := os.Open(file)
- if err != nil {
- return err
- }
- defer f.Close()
- // Gather all the runs of 40 capitalized hex characters.
- br := bufio.NewReader(f)
- var hexBuf bytes.Buffer
- for {
- b, err := br.ReadByte()
- isHex := ('A' <= b && b <= 'F') || ('0' <= b && b <= '9')
- if isHex {
- hexBuf.WriteByte(b)
- } else {
- if hexBuf.Len() == 40 {
- set[hexBuf.String()] = true
- }
- hexBuf.Reset()
- }
- if err == io.EOF {
- break
- }
- if err != nil {
- return err
- }
- }
- return nil
- }
- if err := run("user", "trust-settings-export"); err != nil {
- return nil, fmt.Errorf("dump-trust-settings (user): %v", err)
- }
- if err := run("admin", "trust-settings-export", "-d"); err != nil {
- return nil, fmt.Errorf("dump-trust-settings (admin): %v", err)
- }
- return set, nil
- }
|