root_darwin.go 8.2 KB

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