aboutsummaryrefslogtreecommitdiff
path: root/src/crypto/x509/root_darwin_amd64.go
blob: ce88de025ecb9f48d8f444a3f26b76118a224800 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build !ios

package x509

import (
	"bytes"
	macOS "crypto/x509/internal/macos"
	"fmt"
	"os"
	"strings"
)

var debugDarwinRoots = strings.Contains(os.Getenv("GODEBUG"), "x509roots=1")

func (c *Certificate) systemVerify(opts *VerifyOptions) (chains [][]*Certificate, err error) {
	return nil, nil
}

// loadSystemRootsWithCgo is set in root_cgo_darwin_amd64.go when cgo is
// available, and is only used for testing.
var loadSystemRootsWithCgo func() (*CertPool, error)

func loadSystemRoots() (*CertPool, error) {
	var trustedRoots []*Certificate
	untrustedRoots := make(map[string]bool)

	// macOS has three trust domains: one for CAs added by users to their
	// "login" keychain, one for CAs added by Admins to the "System" keychain,
	// and one for the CAs that ship with the OS.
	for _, domain := range []macOS.SecTrustSettingsDomain{
		macOS.SecTrustSettingsDomainUser,
		macOS.SecTrustSettingsDomainAdmin,
		macOS.SecTrustSettingsDomainSystem,
	} {
		certs, err := macOS.SecTrustSettingsCopyCertificates(domain)
		if err == macOS.ErrNoTrustSettings {
			continue
		} else if err != nil {
			return nil, err
		}
		defer macOS.CFRelease(certs)

		for i := 0; i < macOS.CFArrayGetCount(certs); i++ {
			c := macOS.CFArrayGetValueAtIndex(certs, i)
			cert, err := exportCertificate(c)
			if err != nil {
				if debugDarwinRoots {
					fmt.Fprintf(os.Stderr, "crypto/x509: domain %d, certificate #%d: %v\n", domain, i, err)
				}
				continue
			}

			var result macOS.SecTrustSettingsResult
			if domain == macOS.SecTrustSettingsDomainSystem {
				// Certs found in the system domain are always trusted. If the user
				// configures "Never Trust" on such a cert, it will also be found in the
				// admin or user domain, causing it to be added to untrustedRoots.
				result = macOS.SecTrustSettingsResultTrustRoot
			} else {
				result, err = sslTrustSettingsResult(c)
				if err != nil {
					if debugDarwinRoots {
						fmt.Fprintf(os.Stderr, "crypto/x509: trust settings for %v: %v\n", cert.Subject, err)
					}
					continue
				}
				if debugDarwinRoots {
					fmt.Fprintf(os.Stderr, "crypto/x509: trust settings for %v: %d\n", cert.Subject, result)
				}
			}

			switch result {
			// "Note the distinction between the results kSecTrustSettingsResultTrustRoot
			// and kSecTrustSettingsResultTrustAsRoot: The former can only be applied to
			// root (self-signed) certificates; the latter can only be applied to
			// non-root certificates."
			case macOS.SecTrustSettingsResultTrustRoot:
				if isRootCertificate(cert) {
					trustedRoots = append(trustedRoots, cert)
				}
			case macOS.SecTrustSettingsResultTrustAsRoot:
				if !isRootCertificate(cert) {
					trustedRoots = append(trustedRoots, cert)
				}

			case macOS.SecTrustSettingsResultDeny:
				// Add this certificate to untrustedRoots, which are subtracted
				// from trustedRoots, so that we don't have to evaluate policies
				// for every root in the system domain, but still apply user and
				// admin policies that override system roots.
				untrustedRoots[string(cert.Raw)] = true

			case macOS.SecTrustSettingsResultUnspecified:
				// Certificates with unspecified trust should be added to a pool
				// of intermediates for chain building, but we don't support it
				// at the moment. This is Issue 35631.

			default:
				if debugDarwinRoots {
					fmt.Fprintf(os.Stderr, "crypto/x509: unknown trust setting for %v: %d\n", cert.Subject, result)
				}
			}
		}
	}

	pool := NewCertPool()
	for _, cert := range trustedRoots {
		if !untrustedRoots[string(cert.Raw)] {
			pool.AddCert(cert)
		}
	}
	return pool, nil
}

// exportCertificate returns a *Certificate for a SecCertificateRef.
func exportCertificate(cert macOS.CFRef) (*Certificate, error) {
	data, err := macOS.SecItemExport(cert)
	if err != nil {
		return nil, err
	}
	defer macOS.CFRelease(data)
	der := macOS.CFDataToSlice(data)

	return ParseCertificate(der)
}

// isRootCertificate reports whether Subject and Issuer match.
func isRootCertificate(cert *Certificate) bool {
	return bytes.Equal(cert.RawSubject, cert.RawIssuer)
}

// sslTrustSettingsResult obtains the final kSecTrustSettingsResult value for a
// certificate in the user or admin domain, combining usage constraints for the
// SSL SecTrustSettingsPolicy,
//
// It ignores SecTrustSettingsKeyUsage and kSecTrustSettingsAllowedError, and
// doesn't support kSecTrustSettingsDefaultRootCertSetting.
//
// https://developer.apple.com/documentation/security/1400261-sectrustsettingscopytrustsetting
func sslTrustSettingsResult(cert macOS.CFRef) (macOS.SecTrustSettingsResult, error) {
	// In Apple's implementation user trust settings override admin trust settings
	// (which themselves override system trust settings). If SecTrustSettingsCopyTrustSettings
	// fails, or returns a NULL trust settings, when looking for the user trust
	// settings then fallback to checking the admin trust settings.
	//
	// See Security-59306.41.2/trust/headers/SecTrustSettings.h for a description of
	// the trust settings overrides, and SecLegacyAnchorSourceCopyUsageConstraints in
	// Security-59306.41.2/trust/trustd/SecCertificateSource.c for a concrete example
	// of how Apple applies the override in the case of NULL trust settings, or non
	// success errors.
	trustSettings, err := macOS.SecTrustSettingsCopyTrustSettings(cert, macOS.SecTrustSettingsDomainUser)
	if err != nil || trustSettings == 0 {
		if debugDarwinRoots && err != macOS.ErrNoTrustSettings {
			fmt.Fprintf(os.Stderr, "crypto/x509: SecTrustSettingsCopyTrustSettings for SecTrustSettingsDomainUser failed: %s\n", err)
		}
		trustSettings, err = macOS.SecTrustSettingsCopyTrustSettings(cert, macOS.SecTrustSettingsDomainAdmin)
	}
	if err != nil || trustSettings == 0 {
		// If there are neither user nor admin trust settings for a certificate returned
		// from SecTrustSettingsCopyCertificates Apple returns kSecTrustSettingsResultInvalid,
		// as this method is intended to return certificates _which have trust settings_.
		// The most likely case for this being triggered is that the existing trust settings
		// are invalid and cannot be properly parsed. In this case SecTrustSettingsCopyTrustSettings
		// returns errSecInvalidTrustSettings. The existing cgo implementation returns
		// kSecTrustSettingsResultUnspecified in this case, which mostly matches the Apple
		// implementation because we don't do anything with certificates marked with this
		// result.
		//
		// See SecPVCGetTrustSettingsResult in Security-59306.41.2/trust/trustd/SecPolicyServer.c
		if debugDarwinRoots && err != macOS.ErrNoTrustSettings {
			fmt.Fprintf(os.Stderr, "crypto/x509: SecTrustSettingsCopyTrustSettings for SecTrustSettingsDomainAdmin failed: %s\n", err)
		}
		return macOS.SecTrustSettingsResultUnspecified, nil
	}
	defer macOS.CFRelease(trustSettings)

	// "An empty trust settings array means 'always trust this certificate' with an
	// overall trust setting for the certificate of kSecTrustSettingsResultTrustRoot."
	if macOS.CFArrayGetCount(trustSettings) == 0 {
		return macOS.SecTrustSettingsResultTrustRoot, nil
	}

	isSSLPolicy := func(policyRef macOS.CFRef) bool {
		properties := macOS.SecPolicyCopyProperties(policyRef)
		defer macOS.CFRelease(properties)
		if v, ok := macOS.CFDictionaryGetValueIfPresent(properties, macOS.SecPolicyOid); ok {
			return macOS.CFEqual(v, macOS.CFRef(macOS.SecPolicyAppleSSL))
		}
		return false
	}

	for i := 0; i < macOS.CFArrayGetCount(trustSettings); i++ {
		tSetting := macOS.CFArrayGetValueAtIndex(trustSettings, i)

		// First, check if this trust setting is constrained to a non-SSL policy.
		if policyRef, ok := macOS.CFDictionaryGetValueIfPresent(tSetting, macOS.SecTrustSettingsPolicy); ok {
			if !isSSLPolicy(policyRef) {
				continue
			}
		}

		// Then check if it is restricted to a hostname, so not a root.
		if _, ok := macOS.CFDictionaryGetValueIfPresent(tSetting, macOS.SecTrustSettingsPolicyString); ok {
			continue
		}

		cfNum, ok := macOS.CFDictionaryGetValueIfPresent(tSetting, macOS.SecTrustSettingsResultKey)
		// "If this key is not present, a default value of kSecTrustSettingsResultTrustRoot is assumed."
		if !ok {
			return macOS.SecTrustSettingsResultTrustRoot, nil
		}
		result, err := macOS.CFNumberGetValue(cfNum)
		if err != nil {
			return 0, err
		}

		// If multiple dictionaries match, we are supposed to "OR" them,
		// the semantics of which are not clear. Since TrustRoot and TrustAsRoot
		// are mutually exclusive, Deny should probably override, and Invalid and
		// Unspecified be overridden, approximate this by stopping at the first
		// TrustRoot, TrustAsRoot or Deny.
		switch r := macOS.SecTrustSettingsResult(result); r {
		case macOS.SecTrustSettingsResultTrustRoot,
			macOS.SecTrustSettingsResultTrustAsRoot,
			macOS.SecTrustSettingsResultDeny:
			return r, nil
		}
	}

	// If trust settings are present, but none of them match the policy...
	// the docs don't tell us what to do.
	//
	// "Trust settings for a given use apply if any of the dictionaries in the
	// certificate’s trust settings array satisfies the specified use." suggests
	// that it's as if there were no trust settings at all, so we should maybe
	// fallback to the admin trust settings? TODO(golang.org/issue/38888).

	return macOS.SecTrustSettingsResultUnspecified, nil
}