@@ -155,6 +155,44 @@ func init() {
155
155
}
156
156
}
157
157
158
+ func verifyChain (c * Certificate , chainCtx * syscall.CertChainContext , opts * VerifyOptions ) (chain []* Certificate , err error ) {
159
+ err = checkChainTrustStatus (c , chainCtx )
160
+ if err != nil {
161
+ return nil , err
162
+ }
163
+
164
+ if opts != nil && len (opts .DNSName ) > 0 {
165
+ err = checkChainSSLServerPolicy (c , chainCtx , opts )
166
+ if err != nil {
167
+ return nil , err
168
+ }
169
+ }
170
+
171
+ chain , err = extractSimpleChain (chainCtx .Chains , int (chainCtx .ChainCount ))
172
+ if err != nil {
173
+ return nil , err
174
+ }
175
+ if len (chain ) == 0 {
176
+ return nil , errors .New ("x509: internal error: system verifier returned an empty chain" )
177
+ }
178
+
179
+ // Mitigate CVE-2020-0601, where the Windows system verifier might be
180
+ // tricked into using custom curve parameters for a trusted root, by
181
+ // double-checking all ECDSA signatures. If the system was tricked into
182
+ // using spoofed parameters, the signature will be invalid for the correct
183
+ // ones we parsed. (We don't support custom curves ourselves.)
184
+ for i , parent := range chain [1 :] {
185
+ if parent .PublicKeyAlgorithm != ECDSA {
186
+ continue
187
+ }
188
+ if err := parent .CheckSignature (chain [i ].SignatureAlgorithm ,
189
+ chain [i ].RawTBSCertificate , chain [i ].Signature ); err != nil {
190
+ return nil , err
191
+ }
192
+ }
193
+ return chain , nil
194
+ }
195
+
158
196
// systemVerify is like Verify, except that it uses CryptoAPI calls
159
197
// to build certificate chains and verify them.
160
198
func (c * Certificate ) systemVerify (opts * VerifyOptions ) (chains [][]* Certificate , err error ) {
@@ -202,67 +240,41 @@ func (c *Certificate) systemVerify(opts *VerifyOptions) (chains [][]*Certificate
202
240
verifyTime = & ft
203
241
}
204
242
205
- // CertGetCertificateChain will traverse Windows's root stores
206
- // in an attempt to build a verified certificate chain. Once
207
- // it has found a verified chain, it stops. MSDN docs on
208
- // CERT_CHAIN_CONTEXT:
209
- //
210
- // When a CERT_CHAIN_CONTEXT is built, the first simple chain
211
- // begins with an end certificate and ends with a self-signed
212
- // certificate. If that self-signed certificate is not a root
213
- // or otherwise trusted certificate, an attempt is made to
214
- // build a new chain. CTLs are used to create the new chain
215
- // beginning with the self-signed certificate from the original
216
- // chain as the end certificate of the new chain. This process
217
- // continues building additional simple chains until the first
218
- // self-signed certificate is a trusted certificate or until
219
- // an additional simple chain cannot be built.
220
- //
221
- // The result is that we'll only get a single trusted chain to
222
- // return to our caller.
223
- var chainCtx * syscall.CertChainContext
224
- err = syscall .CertGetCertificateChain (syscall .Handle (0 ), storeCtx , verifyTime , storeCtx .Store , para , 0 , 0 , & chainCtx )
225
- if err != nil {
226
- return nil , err
227
- }
228
- defer syscall .CertFreeCertificateChain (chainCtx )
243
+ // The default is to return only the highest quality chain,
244
+ // setting this flag will add additional lower quality contexts.
245
+ // These are returned in the LowerQualityChains field.
246
+ const CERT_CHAIN_RETURN_LOWER_QUALITY_CONTEXTS = 0x00000080
229
247
230
- err = checkChainTrustStatus (c , chainCtx )
248
+ // CertGetCertificateChain will traverse Windows's root stores in an attempt to build a verified certificate chain
249
+ var topCtx * syscall.CertChainContext
250
+ err = syscall .CertGetCertificateChain (syscall .Handle (0 ), storeCtx , verifyTime , storeCtx .Store , para , CERT_CHAIN_RETURN_LOWER_QUALITY_CONTEXTS , 0 , & topCtx )
231
251
if err != nil {
232
252
return nil , err
233
253
}
254
+ defer syscall .CertFreeCertificateChain (topCtx )
234
255
235
- if opts != nil && len (opts .DNSName ) > 0 {
236
- err = checkChainSSLServerPolicy (c , chainCtx , opts )
237
- if err != nil {
238
- return nil , err
239
- }
256
+ chain , topErr := verifyChain (c , topCtx , opts )
257
+ if topErr == nil {
258
+ chains = append (chains , chain )
240
259
}
241
260
242
- chain , err := extractSimpleChain (chainCtx .Chains , int (chainCtx .ChainCount ))
243
- if err != nil {
244
- return nil , err
245
- }
246
- if len (chain ) < 1 {
247
- return nil , errors .New ("x509: internal error: system verifier returned an empty chain" )
248
- }
261
+ if lqCtxCount := topCtx .LowerQualityChainCount ; lqCtxCount > 0 {
262
+ lqCtxs := (* [1 << 20 ]* syscall.CertChainContext )(unsafe .Pointer (topCtx .LowerQualityChains ))[:lqCtxCount :lqCtxCount ]
249
263
250
- // Mitigate CVE-2020-0601, where the Windows system verifier might be
251
- // tricked into using custom curve parameters for a trusted root, by
252
- // double-checking all ECDSA signatures. If the system was tricked into
253
- // using spoofed parameters, the signature will be invalid for the correct
254
- // ones we parsed. (We don't support custom curves ourselves.)
255
- for i , parent := range chain [1 :] {
256
- if parent .PublicKeyAlgorithm != ECDSA {
257
- continue
258
- }
259
- if err := parent .CheckSignature (chain [i ].SignatureAlgorithm ,
260
- chain [i ].RawTBSCertificate , chain [i ].Signature ); err != nil {
261
- return nil , err
264
+ for _ , ctx := range lqCtxs {
265
+ chain , err := verifyChain (c , ctx , opts )
266
+ if err == nil {
267
+ chains = append (chains , chain )
268
+ }
262
269
}
263
270
}
264
271
265
- return [][]* Certificate {chain }, nil
272
+ if len (chains ) == 0 {
273
+ // Return the error from the highest quality context.
274
+ return nil , topErr
275
+ }
276
+
277
+ return chains , nil
266
278
}
267
279
268
280
func loadSystemRoots () (* CertPool , error ) {
0 commit comments