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 }