github.com/insolar/x-crypto@v0.0.0-20191031140942-75fab8a325f6/x509/root_darwin.go (about)

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