...

Source file src/crypto/x509/root_darwin.go

     1	// Copyright 2013 The Go Authors. All rights reserved.
     2	// Use of this source code is governed by a BSD-style
     3	// license that can be found in the LICENSE file.
     4	
     5	//go:generate go run root_darwin_arm_gen.go -output root_darwin_armx.go
     6	
     7	package x509
     8	
     9	import (
    10		"bufio"
    11		"bytes"
    12		"crypto/sha1"
    13		"encoding/pem"
    14		"fmt"
    15		"io"
    16		"io/ioutil"
    17		"os"
    18		"os/exec"
    19		"path/filepath"
    20		"strings"
    21		"sync"
    22	)
    23	
    24	var debugDarwinRoots = strings.Contains(os.Getenv("GODEBUG"), "x509roots=1")
    25	
    26	func (c *Certificate) systemVerify(opts *VerifyOptions) (chains [][]*Certificate, err error) {
    27		return nil, nil
    28	}
    29	
    30	// This code is only used when compiling without cgo.
    31	// It is here, instead of root_nocgo_darwin.go, so that tests can check it
    32	// even if the tests are run with cgo enabled.
    33	// The linker will not include these unused functions in binaries built with cgo enabled.
    34	
    35	// execSecurityRoots finds the macOS list of trusted root certificates
    36	// using only command-line tools. This is our fallback path when cgo isn't available.
    37	//
    38	// The strategy is as follows:
    39	//
    40	// 1. Run "security trust-settings-export" and "security
    41	//    trust-settings-export -d" to discover the set of certs with some
    42	//    user-tweaked trust policy. We're too lazy to parse the XML
    43	//    (Issue 26830) to understand what the trust
    44	//    policy actually is. We just learn that there is _some_ policy.
    45	//
    46	// 2. Run "security find-certificate" to dump the list of system root
    47	//    CAs in PEM format.
    48	//
    49	// 3. For each dumped cert, conditionally verify it with "security
    50	//    verify-cert" if that cert was in the set discovered in Step 1.
    51	//    Without the Step 1 optimization, running "security verify-cert"
    52	//    150-200 times takes 3.5 seconds. With the optimization, the
    53	//    whole process takes about 180 milliseconds with 1 untrusted root
    54	//    CA. (Compared to 110ms in the cgo path)
    55	func execSecurityRoots() (*CertPool, error) {
    56		hasPolicy, err := getCertsWithTrustPolicy()
    57		if err != nil {
    58			return nil, err
    59		}
    60		if debugDarwinRoots {
    61			fmt.Fprintf(os.Stderr, "crypto/x509: %d certs have a trust policy\n", len(hasPolicy))
    62		}
    63	
    64		keychains := []string{"/Library/Keychains/System.keychain"}
    65	
    66		// Note that this results in trusting roots from $HOME/... (the environment
    67		// variable), which might not be expected.
    68		home, err := os.UserHomeDir()
    69		if err != nil {
    70			if debugDarwinRoots {
    71				fmt.Fprintf(os.Stderr, "crypto/x509: can't get user home directory: %v\n", err)
    72			}
    73		} else {
    74			keychains = append(keychains,
    75				filepath.Join(home, "/Library/Keychains/login.keychain"),
    76	
    77				// Fresh installs of Sierra use a slightly different path for the login keychain
    78				filepath.Join(home, "/Library/Keychains/login.keychain-db"),
    79			)
    80		}
    81	
    82		type rootCandidate struct {
    83			c      *Certificate
    84			system bool
    85		}
    86	
    87		var (
    88			mu          sync.Mutex
    89			roots       = NewCertPool()
    90			numVerified int // number of execs of 'security verify-cert', for debug stats
    91			wg          sync.WaitGroup
    92			verifyCh    = make(chan rootCandidate)
    93		)
    94	
    95		// Using 4 goroutines to pipe into verify-cert seems to be
    96		// about the best we can do. The verify-cert binary seems to
    97		// just RPC to another server with coarse locking anyway, so
    98		// running 16 at a time for instance doesn't help at all. Due
    99		// to the "if hasPolicy" check below, though, we will rarely
   100		// (or never) call verify-cert on stock macOS systems, though.
   101		// The hope is that we only call verify-cert when the user has
   102		// tweaked their trust policy. These 4 goroutines are only
   103		// defensive in the pathological case of many trust edits.
   104		for i := 0; i < 4; i++ {
   105			wg.Add(1)
   106			go func() {
   107				defer wg.Done()
   108				for cert := range verifyCh {
   109					sha1CapHex := fmt.Sprintf("%X", sha1.Sum(cert.c.Raw))
   110	
   111					var valid bool
   112					verifyChecks := 0
   113					if hasPolicy[sha1CapHex] {
   114						verifyChecks++
   115						valid = verifyCertWithSystem(cert.c)
   116					} else {
   117						// Certificates not in SystemRootCertificates without user
   118						// or admin trust settings are not trusted.
   119						valid = cert.system
   120					}
   121	
   122					mu.Lock()
   123					numVerified += verifyChecks
   124					if valid {
   125						roots.AddCert(cert.c)
   126					}
   127					mu.Unlock()
   128				}
   129			}()
   130		}
   131		err = forEachCertInKeychains(keychains, func(cert *Certificate) {
   132			verifyCh <- rootCandidate{c: cert, system: false}
   133		})
   134		if err != nil {
   135			close(verifyCh)
   136			return nil, err
   137		}
   138		err = forEachCertInKeychains([]string{
   139			"/System/Library/Keychains/SystemRootCertificates.keychain",
   140		}, func(cert *Certificate) {
   141			verifyCh <- rootCandidate{c: cert, system: true}
   142		})
   143		if err != nil {
   144			close(verifyCh)
   145			return nil, err
   146		}
   147		close(verifyCh)
   148		wg.Wait()
   149	
   150		if debugDarwinRoots {
   151			fmt.Fprintf(os.Stderr, "crypto/x509: ran security verify-cert %d times\n", numVerified)
   152		}
   153	
   154		return roots, nil
   155	}
   156	
   157	func forEachCertInKeychains(paths []string, f func(*Certificate)) error {
   158		args := append([]string{"find-certificate", "-a", "-p"}, paths...)
   159		cmd := exec.Command("/usr/bin/security", args...)
   160		data, err := cmd.Output()
   161		if err != nil {
   162			return err
   163		}
   164		for len(data) > 0 {
   165			var block *pem.Block
   166			block, data = pem.Decode(data)
   167			if block == nil {
   168				break
   169			}
   170			if block.Type != "CERTIFICATE" || len(block.Headers) != 0 {
   171				continue
   172			}
   173			cert, err := ParseCertificate(block.Bytes)
   174			if err != nil {
   175				continue
   176			}
   177			f(cert)
   178		}
   179		return nil
   180	}
   181	
   182	func verifyCertWithSystem(cert *Certificate) bool {
   183		data := pem.EncodeToMemory(&pem.Block{
   184			Type: "CERTIFICATE", Bytes: cert.Raw,
   185		})
   186	
   187		f, err := ioutil.TempFile("", "cert")
   188		if err != nil {
   189			fmt.Fprintf(os.Stderr, "can't create temporary file for cert: %v", err)
   190			return false
   191		}
   192		defer os.Remove(f.Name())
   193		if _, err := f.Write(data); err != nil {
   194			fmt.Fprintf(os.Stderr, "can't write temporary file for cert: %v", err)
   195			return false
   196		}
   197		if err := f.Close(); err != nil {
   198			fmt.Fprintf(os.Stderr, "can't write temporary file for cert: %v", err)
   199			return false
   200		}
   201		cmd := exec.Command("/usr/bin/security", "verify-cert", "-p", "ssl", "-c", f.Name(), "-l", "-L")
   202		var stderr bytes.Buffer
   203		if debugDarwinRoots {
   204			cmd.Stderr = &stderr
   205		}
   206		if err := cmd.Run(); err != nil {
   207			if debugDarwinRoots {
   208				fmt.Fprintf(os.Stderr, "crypto/x509: verify-cert rejected %s: %q\n", cert.Subject, bytes.TrimSpace(stderr.Bytes()))
   209			}
   210			return false
   211		}
   212		if debugDarwinRoots {
   213			fmt.Fprintf(os.Stderr, "crypto/x509: verify-cert approved %s\n", cert.Subject)
   214		}
   215		return true
   216	}
   217	
   218	// getCertsWithTrustPolicy returns the set of certs that have a
   219	// possibly-altered trust policy. The keys of the map are capitalized
   220	// sha1 hex of the raw cert.
   221	// They are the certs that should be checked against `security
   222	// verify-cert` to see whether the user altered the default trust
   223	// settings. This code is only used for cgo-disabled builds.
   224	func getCertsWithTrustPolicy() (map[string]bool, error) {
   225		set := map[string]bool{}
   226		td, err := ioutil.TempDir("", "x509trustpolicy")
   227		if err != nil {
   228			return nil, err
   229		}
   230		defer os.RemoveAll(td)
   231		run := func(file string, args ...string) error {
   232			file = filepath.Join(td, file)
   233			args = append(args, file)
   234			cmd := exec.Command("/usr/bin/security", args...)
   235			var stderr bytes.Buffer
   236			cmd.Stderr = &stderr
   237			if err := cmd.Run(); err != nil {
   238				// If there are no trust settings, the
   239				// `security trust-settings-export` command
   240				// fails with:
   241				//    exit status 1, SecTrustSettingsCreateExternalRepresentation: No Trust Settings were found.
   242				// Rather than match on English substrings that are probably
   243				// localized on macOS, just interpret any failure to mean that
   244				// there are no trust settings.
   245				if debugDarwinRoots {
   246					fmt.Fprintf(os.Stderr, "crypto/x509: exec %q: %v, %s\n", cmd.Args, err, stderr.Bytes())
   247				}
   248				return nil
   249			}
   250	
   251			f, err := os.Open(file)
   252			if err != nil {
   253				return err
   254			}
   255			defer f.Close()
   256	
   257			// Gather all the runs of 40 capitalized hex characters.
   258			br := bufio.NewReader(f)
   259			var hexBuf bytes.Buffer
   260			for {
   261				b, err := br.ReadByte()
   262				isHex := ('A' <= b && b <= 'F') || ('0' <= b && b <= '9')
   263				if isHex {
   264					hexBuf.WriteByte(b)
   265				} else {
   266					if hexBuf.Len() == 40 {
   267						set[hexBuf.String()] = true
   268					}
   269					hexBuf.Reset()
   270				}
   271				if err == io.EOF {
   272					break
   273				}
   274				if err != nil {
   275					return err
   276				}
   277			}
   278	
   279			return nil
   280		}
   281		if err := run("user", "trust-settings-export"); err != nil {
   282			return nil, fmt.Errorf("dump-trust-settings (user): %v", err)
   283		}
   284		if err := run("admin", "trust-settings-export", "-d"); err != nil {
   285			return nil, fmt.Errorf("dump-trust-settings (admin): %v", err)
   286		}
   287		return set, nil
   288	}
   289	

View as plain text