root_darwin.go 7.5 KB

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