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 debugExecDarwinRoots = 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 (at 43 // least at this stage of Go 1.8) 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 debugExecDarwinRoots { 61 println(fmt.Sprintf("crypto/x509: %d certs have a trust policy", len(hasPolicy))) 62 } 63 64 cmd := exec.Command("/usr/bin/security", "find-certificate", "-a", "-p", "/System/Library/Keychains/SystemRootCertificates.keychain") 65 data, err := cmd.Output() 66 if err != nil { 67 return nil, err 68 } 69 70 var ( 71 mu sync.Mutex 72 roots = NewCertPool() 73 numVerified int // number of execs of 'security verify-cert', for debug stats 74 ) 75 76 blockCh := make(chan *pem.Block) 77 var wg sync.WaitGroup 78 79 // Using 4 goroutines to pipe into verify-cert seems to be 80 // about the best we can do. The verify-cert binary seems to 81 // just RPC to another server with coarse locking anyway, so 82 // running 16 at a time for instance doesn't help at all. Due 83 // to the "if hasPolicy" check below, though, we will rarely 84 // (or never) call verify-cert on stock macOS systems, though. 85 // The hope is that we only call verify-cert when the user has 86 // tweaked their trust policy. These 4 goroutines are only 87 // defensive in the pathological case of many trust edits. 88 for i := 0; i < 4; i++ { 89 wg.Add(1) 90 go func() { 91 defer wg.Done() 92 for block := range blockCh { 93 cert, err := ParseCertificate(block.Bytes) 94 if err != nil { 95 continue 96 } 97 sha1CapHex := fmt.Sprintf("%X", sha1.Sum(block.Bytes)) 98 99 valid := true 100 verifyChecks := 0 101 if hasPolicy[sha1CapHex] { 102 verifyChecks++ 103 if !verifyCertWithSystem(block, cert) { 104 valid = false 105 } 106 } 107 108 mu.Lock() 109 numVerified += verifyChecks 110 if valid { 111 roots.AddCert(cert) 112 } 113 mu.Unlock() 114 } 115 }() 116 } 117 for len(data) > 0 { 118 var block *pem.Block 119 block, data = pem.Decode(data) 120 if block == nil { 121 break 122 } 123 if block.Type != "CERTIFICATE" || len(block.Headers) != 0 { 124 continue 125 } 126 blockCh <- block 127 } 128 close(blockCh) 129 wg.Wait() 130 131 if debugExecDarwinRoots { 132 mu.Lock() 133 defer mu.Unlock() 134 println(fmt.Sprintf("crypto/x509: ran security verify-cert %d times", numVerified)) 135 } 136 137 return roots, nil 138 } 139 140 func verifyCertWithSystem(block *pem.Block, cert *Certificate) bool { 141 data := pem.EncodeToMemory(block) 142 143 f, err := ioutil.TempFile("", "cert") 144 if err != nil { 145 fmt.Fprintf(os.Stderr, "can't create temporary file for cert: %v", err) 146 return false 147 } 148 defer os.Remove(f.Name()) 149 if _, err := f.Write(data); err != nil { 150 fmt.Fprintf(os.Stderr, "can't write temporary file for cert: %v", err) 151 return false 152 } 153 if err := f.Close(); err != nil { 154 fmt.Fprintf(os.Stderr, "can't write temporary file for cert: %v", err) 155 return false 156 } 157 cmd := exec.Command("/usr/bin/security", "verify-cert", "-c", f.Name(), "-l", "-L") 158 var stderr bytes.Buffer 159 if debugExecDarwinRoots { 160 cmd.Stderr = &stderr 161 } 162 if err := cmd.Run(); err != nil { 163 if debugExecDarwinRoots { 164 println(fmt.Sprintf("crypto/x509: verify-cert rejected %s: %q", cert.Subject.CommonName, bytes.TrimSpace(stderr.Bytes()))) 165 } 166 return false 167 } 168 if debugExecDarwinRoots { 169 println(fmt.Sprintf("crypto/x509: verify-cert approved %s", cert.Subject.CommonName)) 170 } 171 return true 172 } 173 174 // getCertsWithTrustPolicy returns the set of certs that have a 175 // possibly-altered trust policy. The keys of the map are capitalized 176 // sha1 hex of the raw cert. 177 // They are the certs that should be checked against `security 178 // verify-cert` to see whether the user altered the default trust 179 // settings. This code is only used for cgo-disabled builds. 180 func getCertsWithTrustPolicy() (map[string]bool, error) { 181 set := map[string]bool{} 182 td, err := ioutil.TempDir("", "x509trustpolicy") 183 if err != nil { 184 return nil, err 185 } 186 defer os.RemoveAll(td) 187 run := func(file string, args ...string) error { 188 file = filepath.Join(td, file) 189 args = append(args, file) 190 cmd := exec.Command("/usr/bin/security", args...) 191 var stderr bytes.Buffer 192 cmd.Stderr = &stderr 193 if err := cmd.Run(); err != nil { 194 // If there are no trust settings, the 195 // `security trust-settings-export` command 196 // fails with: 197 // exit status 1, SecTrustSettingsCreateExternalRepresentation: No Trust Settings were found. 198 // Rather than match on English substrings that are probably 199 // localized on macOS, just interpret any failure to mean that 200 // there are no trust settings. 201 if debugExecDarwinRoots { 202 println(fmt.Sprintf("crypto/x509: exec %q: %v, %s", cmd.Args, err, stderr.Bytes())) 203 } 204 return nil 205 } 206 207 f, err := os.Open(file) 208 if err != nil { 209 return err 210 } 211 defer f.Close() 212 213 // Gather all the runs of 40 capitalized hex characters. 214 br := bufio.NewReader(f) 215 var hexBuf bytes.Buffer 216 for { 217 b, err := br.ReadByte() 218 isHex := ('A' <= b && b <= 'F') || ('0' <= b && b <= '9') 219 if isHex { 220 hexBuf.WriteByte(b) 221 } else { 222 if hexBuf.Len() == 40 { 223 set[hexBuf.String()] = true 224 } 225 hexBuf.Reset() 226 } 227 if err == io.EOF { 228 break 229 } 230 if err != nil { 231 return err 232 } 233 } 234 235 return nil 236 } 237 if err := run("user", "trust-settings-export"); err != nil { 238 return nil, fmt.Errorf("dump-trust-settings (user): %v", err) 239 } 240 if err := run("admin", "trust-settings-export", "-d"); err != nil { 241 return nil, fmt.Errorf("dump-trust-settings (admin): %v", err) 242 } 243 return set, nil 244 } 245