blob: 25cbcd1cdef07fd991a8f4cc04f70b97342d6edc [file] [log] [blame]
// 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
}