| // 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" |
| "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))) |
| } |
| |
| cmd := exec.Command("/usr/bin/security", "find-certificate", "-a", "-p", "/System/Library/Keychains/SystemRootCertificates.keychain") |
| 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 |
| } |