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 }