1
2
3
4
5
6
7 package x509
8
9 import (
10 "bufio"
11 "bytes"
12 "crypto/sha1"
13 "encoding/pem"
14 "fmt"
15 "io"
16 "os"
17 "os/exec"
18 "os/user"
19 "path/filepath"
20 "strings"
21 "sync"
22 )
23
24 var debugDarwinRoots = 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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55 func execSecurityRoots() (*CertPool, error) {
56 hasPolicy, err := getCertsWithTrustPolicy()
57 if err != nil {
58 return nil, err
59 }
60 if debugDarwinRoots {
61 fmt.Fprintf(os.Stderr, "crypto/x509: %d certs have a trust policy\n", len(hasPolicy))
62 }
63
64 keychains := []string{"/Library/Keychains/System.keychain"}
65
66
67
68 u, err := user.Current()
69 if err != nil {
70 if debugDarwinRoots {
71 fmt.Fprintf(os.Stderr, "crypto/x509: can't get user home directory: %v\n", err)
72 }
73 } else {
74 keychains = append(keychains,
75 filepath.Join(u.HomeDir, "/Library/Keychains/login.keychain"),
76
77
78 filepath.Join(u.HomeDir, "/Library/Keychains/login.keychain-db"),
79 )
80 }
81
82 type rootCandidate struct {
83 c *Certificate
84 system bool
85 }
86
87 var (
88 mu sync.Mutex
89 roots = NewCertPool()
90 numVerified int
91 wg sync.WaitGroup
92 verifyCh = make(chan rootCandidate)
93 )
94
95
96
97
98
99
100
101
102
103
104 for i := 0; i < 4; i++ {
105 wg.Add(1)
106 go func() {
107 defer wg.Done()
108 for cert := range verifyCh {
109 sha1CapHex := fmt.Sprintf("%X", sha1.Sum(cert.c.Raw))
110
111 var valid bool
112 verifyChecks := 0
113 if hasPolicy[sha1CapHex] {
114 verifyChecks++
115 valid = verifyCertWithSystem(cert.c)
116 } else {
117
118
119 valid = cert.system
120 }
121
122 mu.Lock()
123 numVerified += verifyChecks
124 if valid {
125 roots.AddCert(cert.c)
126 }
127 mu.Unlock()
128 }
129 }()
130 }
131 err = forEachCertInKeychains(keychains, func(cert *Certificate) {
132 verifyCh <- rootCandidate{c: cert, system: false}
133 })
134 if err != nil {
135 close(verifyCh)
136 return nil, err
137 }
138 err = forEachCertInKeychains([]string{
139 "/System/Library/Keychains/SystemRootCertificates.keychain",
140 }, func(cert *Certificate) {
141 verifyCh <- rootCandidate{c: cert, system: true}
142 })
143 if err != nil {
144 close(verifyCh)
145 return nil, err
146 }
147 close(verifyCh)
148 wg.Wait()
149
150 if debugDarwinRoots {
151 fmt.Fprintf(os.Stderr, "crypto/x509: ran security verify-cert %d times\n", numVerified)
152 }
153
154 return roots, nil
155 }
156
157 func forEachCertInKeychains(paths []string, f func(*Certificate)) error {
158 args := append([]string{"find-certificate", "-a", "-p"}, paths...)
159 cmd := exec.Command("/usr/bin/security", args...)
160 data, err := cmd.Output()
161 if err != nil {
162 return err
163 }
164 for len(data) > 0 {
165 var block *pem.Block
166 block, data = pem.Decode(data)
167 if block == nil {
168 break
169 }
170 if block.Type != "CERTIFICATE" || len(block.Headers) != 0 {
171 continue
172 }
173 cert, err := ParseCertificate(block.Bytes)
174 if err != nil {
175 continue
176 }
177 f(cert)
178 }
179 return nil
180 }
181
182 func verifyCertWithSystem(cert *Certificate) bool {
183 data := pem.EncodeToMemory(&pem.Block{
184 Type: "CERTIFICATE", Bytes: cert.Raw,
185 })
186
187 f, err := os.CreateTemp("", "cert")
188 if err != nil {
189 fmt.Fprintf(os.Stderr, "can't create temporary file for cert: %v", err)
190 return false
191 }
192 defer os.Remove(f.Name())
193 if _, err := f.Write(data); err != nil {
194 fmt.Fprintf(os.Stderr, "can't write temporary file for cert: %v", err)
195 return false
196 }
197 if err := f.Close(); err != nil {
198 fmt.Fprintf(os.Stderr, "can't write temporary file for cert: %v", err)
199 return false
200 }
201 cmd := exec.Command("/usr/bin/security", "verify-cert", "-p", "ssl", "-c", f.Name(), "-l", "-L")
202 var stderr bytes.Buffer
203 if debugDarwinRoots {
204 cmd.Stderr = &stderr
205 }
206 if err := cmd.Run(); err != nil {
207 if debugDarwinRoots {
208 fmt.Fprintf(os.Stderr, "crypto/x509: verify-cert rejected %s: %q\n", cert.Subject, bytes.TrimSpace(stderr.Bytes()))
209 }
210 return false
211 }
212 if debugDarwinRoots {
213 fmt.Fprintf(os.Stderr, "crypto/x509: verify-cert approved %s\n", cert.Subject)
214 }
215 return true
216 }
217
218
219
220
221
222
223
224 func getCertsWithTrustPolicy() (map[string]bool, error) {
225 set := map[string]bool{}
226 td, err := os.MkdirTemp("", "x509trustpolicy")
227 if err != nil {
228 return nil, err
229 }
230 defer os.RemoveAll(td)
231 run := func(file string, args ...string) error {
232 file = filepath.Join(td, file)
233 args = append(args, file)
234 cmd := exec.Command("/usr/bin/security", args...)
235 var stderr bytes.Buffer
236 cmd.Stderr = &stderr
237 if err := cmd.Run(); err != nil {
238
239
240
241
242
243
244
245 if debugDarwinRoots {
246 fmt.Fprintf(os.Stderr, "crypto/x509: exec %q: %v, %s\n", cmd.Args, err, stderr.Bytes())
247 }
248 return nil
249 }
250
251 f, err := os.Open(file)
252 if err != nil {
253 return err
254 }
255 defer f.Close()
256
257
258 br := bufio.NewReader(f)
259 var hexBuf bytes.Buffer
260 for {
261 b, err := br.ReadByte()
262 isHex := ('A' <= b && b <= 'F') || ('0' <= b && b <= '9')
263 if isHex {
264 hexBuf.WriteByte(b)
265 } else {
266 if hexBuf.Len() == 40 {
267 set[hexBuf.String()] = true
268 }
269 hexBuf.Reset()
270 }
271 if err == io.EOF {
272 break
273 }
274 if err != nil {
275 return err
276 }
277 }
278
279 return nil
280 }
281 if err := run("user", "trust-settings-export"); err != nil {
282 return nil, fmt.Errorf("dump-trust-settings (user): %v", err)
283 }
284 if err := run("admin", "trust-settings-export", "-d"); err != nil {
285 return nil, fmt.Errorf("dump-trust-settings (admin): %v", err)
286 }
287 return set, nil
288 }
289
View as plain text