aboutsummaryrefslogtreecommitdiff
path: root/src/crypto/x509/root_darwin.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/crypto/x509/root_darwin.go')
-rw-r--r--src/crypto/x509/root_darwin.go114
1 files changed, 111 insertions, 3 deletions
diff --git a/src/crypto/x509/root_darwin.go b/src/crypto/x509/root_darwin.go
index 78de56c221..59b303d64f 100644
--- a/src/crypto/x509/root_darwin.go
+++ b/src/crypto/x509/root_darwin.go
@@ -6,12 +6,27 @@
package x509
-import "os/exec"
+import (
+ "bytes"
+ "encoding/pem"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "strconv"
+ "sync"
+ "syscall"
+)
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.
+
func execSecurityRoots() (*CertPool, error) {
cmd := exec.Command("/usr/bin/security", "find-certificate", "-a", "-p", "/System/Library/Keychains/SystemRootCertificates.keychain")
data, err := cmd.Output()
@@ -19,7 +34,100 @@ func execSecurityRoots() (*CertPool, error) {
return nil, err
}
- roots := NewCertPool()
- roots.AppendCertsFromPEM(data)
+ var (
+ mu sync.Mutex
+ roots = NewCertPool()
+ )
+ add := func(cert *Certificate) {
+ mu.Lock()
+ defer mu.Unlock()
+ roots.AddCert(cert)
+ }
+ blockCh := make(chan *pem.Block)
+ var wg sync.WaitGroup
+ for i := 0; i < 4; i++ {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ for block := range blockCh {
+ verifyCertWithSystem(block, add)
+ }
+ }()
+ }
+ 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()
return roots, nil
}
+
+func verifyCertWithSystem(block *pem.Block, add func(*Certificate)) {
+ data := pem.EncodeToMemory(block)
+ var cmd *exec.Cmd
+ if needsTmpFiles() {
+ f, err := ioutil.TempFile("", "cert")
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "can't create temporary file for cert: %v", err)
+ return
+ }
+ 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
+ }
+ if err := f.Close(); err != nil {
+ fmt.Fprintf(os.Stderr, "can't write temporary file for cert: %v", err)
+ return
+ }
+ cmd = exec.Command("/usr/bin/security", "verify-cert", "-c", f.Name(), "-l")
+ } else {
+ cmd = exec.Command("/usr/bin/security", "verify-cert", "-c", "/dev/stdin", "-l")
+ cmd.Stdin = bytes.NewReader(data)
+ }
+ if cmd.Run() == nil {
+ // Non-zero exit means untrusted
+ cert, err := ParseCertificate(block.Bytes)
+ if err != nil {
+ return
+ }
+
+ add(cert)
+ }
+}
+
+var versionCache struct {
+ sync.Once
+ major int
+}
+
+// needsTmpFiles reports whether the OS is <= 10.11 (which requires real
+// files as arguments to the security command).
+func needsTmpFiles() bool {
+ versionCache.Do(func() {
+ release, err := syscall.Sysctl("kern.osrelease")
+ if err != nil {
+ return
+ }
+ for i, c := range release {
+ if c == '.' {
+ release = release[:i]
+ break
+ }
+ }
+ major, err := strconv.Atoi(release)
+ if err != nil {
+ return
+ }
+ versionCache.major = major
+ })
+ return versionCache.major <= 15
+}