golang.org/x/exp@v0.0.0-20240506185415-9bf2ced13842/cmd/macos-roots-test/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  package main
     6  
     7  import (
     8  	"bytes"
     9  	"crypto/x509"
    10  	"encoding/pem"
    11  	"fmt"
    12  	"os"
    13  	"os/exec"
    14  	"os/user"
    15  	"path/filepath"
    16  	"sync"
    17  )
    18  
    19  var debugDarwinRoots = true
    20  
    21  // This code is only used when compiling without cgo.
    22  // It is here, instead of root_nocgo_darwin.go, so that tests can check it
    23  // even if the tests are run with cgo enabled.
    24  // The linker will not include these unused functions in binaries built with cgo enabled.
    25  
    26  // execSecurityRoots finds the macOS list of trusted root certificates
    27  // using only command-line tools. This is our fallback path when cgo isn't available.
    28  //
    29  // The strategy is as follows:
    30  //
    31  //  1. Run "security find-certificate" to dump the list of system root
    32  //     CAs in PEM format.
    33  //
    34  //  2. For each dumped cert, conditionally verify it with "security
    35  //     verify-cert" if that cert was not in the SystemRootCertificates
    36  //     keychain, which can't have custom trust policies.
    37  //
    38  // We need to run "verify-cert" for all certificates not in SystemRootCertificates
    39  // because there might be certificates in the keychains without a corresponding
    40  // trust entry, in which case the logic is complicated (see root_cgo_darwin.go).
    41  //
    42  // TODO: actually parse the "trust-settings-export" output and apply the full
    43  // logic. See Issue 26830.
    44  func execSecurityRoots() (*x509.CertPool, error) {
    45  	keychains := []string{"/Library/Keychains/System.keychain"}
    46  
    47  	// Note that this results in trusting roots from $HOME/... (the environment
    48  	// variable), which might not be expected.
    49  	u, err := user.Current()
    50  	if err != nil {
    51  		if debugDarwinRoots {
    52  			fmt.Printf("crypto/x509: get current user: %v\n", err)
    53  		}
    54  	} else {
    55  		keychains = append(keychains,
    56  			filepath.Join(u.HomeDir, "/Library/Keychains/login.keychain"),
    57  
    58  			// Fresh installs of Sierra use a slightly different path for the login keychain
    59  			filepath.Join(u.HomeDir, "/Library/Keychains/login.keychain-db"),
    60  		)
    61  	}
    62  
    63  	var (
    64  		mu          sync.Mutex
    65  		roots       = x509.NewCertPool()
    66  		numVerified int // number of execs of 'security verify-cert', for debug stats
    67  		wg          sync.WaitGroup
    68  		verifyCh    = make(chan *x509.Certificate)
    69  	)
    70  
    71  	// Using 4 goroutines to pipe into verify-cert seems to be
    72  	// about the best we can do. The verify-cert binary seems to
    73  	// just RPC to another server with coarse locking anyway, so
    74  	// running 16 at a time for instance doesn't help at all.
    75  	for i := 0; i < 4; i++ {
    76  		wg.Add(1)
    77  		go func() {
    78  			defer wg.Done()
    79  			for cert := range verifyCh {
    80  				valid := verifyCertWithSystem(cert)
    81  
    82  				mu.Lock()
    83  				numVerified++
    84  				if valid {
    85  					roots.AddCert(cert)
    86  				}
    87  				mu.Unlock()
    88  			}
    89  		}()
    90  	}
    91  	err = forEachCertInKeychains(keychains, func(cert *x509.Certificate) {
    92  		verifyCh <- cert
    93  	})
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  	close(verifyCh)
    98  	wg.Wait()
    99  
   100  	if debugDarwinRoots {
   101  		fmt.Printf("crypto/x509: ran security verify-cert %d times\n", numVerified)
   102  	}
   103  
   104  	err = forEachCertInKeychains([]string{
   105  		"/System/Library/Keychains/SystemRootCertificates.keychain",
   106  	}, roots.AddCert)
   107  	if err != nil {
   108  		return nil, err
   109  	}
   110  
   111  	return roots, nil
   112  }
   113  
   114  func forEachCertInKeychains(paths []string, f func(*x509.Certificate)) error {
   115  	args := append([]string{"find-certificate", "-a", "-p"}, paths...)
   116  	cmd := exec.Command("/usr/bin/security", args...)
   117  	data, err := cmd.Output()
   118  	if err != nil {
   119  		return err
   120  	}
   121  	for len(data) > 0 {
   122  		var block *pem.Block
   123  		block, data = pem.Decode(data)
   124  		if block == nil {
   125  			break
   126  		}
   127  		if block.Type != "CERTIFICATE" || len(block.Headers) != 0 {
   128  			continue
   129  		}
   130  		cert, err := x509.ParseCertificate(block.Bytes)
   131  		if err != nil {
   132  			continue
   133  		}
   134  		f(cert)
   135  	}
   136  	return nil
   137  }
   138  
   139  func verifyCertWithSystem(cert *x509.Certificate) bool {
   140  	data := pem.EncodeToMemory(&pem.Block{
   141  		Type: "CERTIFICATE", Bytes: cert.Raw,
   142  	})
   143  
   144  	f, err := os.CreateTemp("", "cert")
   145  	if err != nil {
   146  		fmt.Fprintf(os.Stderr, "can't create temporary file for cert: %v", err)
   147  		return false
   148  	}
   149  	defer os.Remove(f.Name())
   150  	if _, err := f.Write(data); err != nil {
   151  		fmt.Fprintf(os.Stderr, "can't write temporary file for cert: %v", err)
   152  		return false
   153  	}
   154  	if err := f.Close(); err != nil {
   155  		fmt.Fprintf(os.Stderr, "can't write temporary file for cert: %v", err)
   156  		return false
   157  	}
   158  	cmd := exec.Command("/usr/bin/security", "verify-cert", "-p", "ssl", "-c", f.Name(), "-l", "-L")
   159  	var stderr bytes.Buffer
   160  	if debugDarwinRoots {
   161  		cmd.Stderr = &stderr
   162  	}
   163  	if err := cmd.Run(); err != nil {
   164  		if debugDarwinRoots {
   165  			fmt.Printf("crypto/x509: verify-cert rejected %s: %q\n", cert.Subject, bytes.TrimSpace(stderr.Bytes()))
   166  		}
   167  		return false
   168  	}
   169  	if debugDarwinRoots {
   170  		fmt.Printf("crypto/x509: verify-cert approved %s\n", cert.Subject)
   171  	}
   172  	return true
   173  }