Source file src/crypto/x509/root_darwin.go

Documentation: crypto/x509

     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