-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
/
certstore_windows.go
916 lines (818 loc) · 28.7 KB
/
certstore_windows.go
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
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
// Copyright 2022-2024 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Adapted, updated, and enhanced from CertToStore, https://github.com/google/certtostore/releases/tag/v1.0.2
// Apache License, Version 2.0, Copyright 2017 Google Inc.
package certstore
import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"encoding/binary"
"fmt"
"io"
"math/big"
"reflect"
"sync"
"syscall"
"unicode/utf16"
"unsafe"
"golang.org/x/crypto/cryptobyte"
"golang.org/x/crypto/cryptobyte/asn1"
"golang.org/x/sys/windows"
)
const (
// wincrypt.h constants
winAcquireCached = 0x1 // CRYPT_ACQUIRE_CACHE_FLAG
winAcquireSilent = 0x40 // CRYPT_ACQUIRE_SILENT_FLAG
winAcquireOnlyNCryptKey = 0x40000 // CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG
winEncodingX509ASN = 1 // X509_ASN_ENCODING
winEncodingPKCS7 = 65536 // PKCS_7_ASN_ENCODING
winCertStoreProvSystem = 10 // CERT_STORE_PROV_SYSTEM
winCertStoreCurrentUser = uint32(winCertStoreCurrentUserID << winCompareShift) // CERT_SYSTEM_STORE_CURRENT_USER
winCertStoreLocalMachine = uint32(winCertStoreLocalMachineID << winCompareShift) // CERT_SYSTEM_STORE_LOCAL_MACHINE
winCertStoreCurrentUserID = 1 // CERT_SYSTEM_STORE_CURRENT_USER_ID
winCertStoreLocalMachineID = 2 // CERT_SYSTEM_STORE_LOCAL_MACHINE_ID
winInfoIssuerFlag = 4 // CERT_INFO_ISSUER_FLAG
winInfoSubjectFlag = 7 // CERT_INFO_SUBJECT_FLAG
winCompareNameStrW = 8 // CERT_COMPARE_NAME_STR_A
winCompareShift = 16 // CERT_COMPARE_SHIFT
// Reference https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certfindcertificateinstore
winFindIssuerStr = winCompareNameStrW<<winCompareShift | winInfoIssuerFlag // CERT_FIND_ISSUER_STR_W
winFindSubjectStr = winCompareNameStrW<<winCompareShift | winInfoSubjectFlag // CERT_FIND_SUBJECT_STR_W
winNcryptKeySpec = 0xFFFFFFFF // CERT_NCRYPT_KEY_SPEC
winBCryptPadPKCS1 uintptr = 0x2
winBCryptPadPSS uintptr = 0x8 // Modern TLS 1.2+
winBCryptPadPSSSalt uint32 = 32 // default 20, 32 optimal for typical SHA256 hash
winRSA1Magic = 0x31415352 // "RSA1" BCRYPT_RSAPUBLIC_MAGIC
winECS1Magic = 0x31534345 // "ECS1" BCRYPT_ECDSA_PUBLIC_P256_MAGIC
winECS3Magic = 0x33534345 // "ECS3" BCRYPT_ECDSA_PUBLIC_P384_MAGIC
winECS5Magic = 0x35534345 // "ECS5" BCRYPT_ECDSA_PUBLIC_P521_MAGIC
winECK1Magic = 0x314B4345 // "ECK1" BCRYPT_ECDH_PUBLIC_P256_MAGIC
winECK3Magic = 0x334B4345 // "ECK3" BCRYPT_ECDH_PUBLIC_P384_MAGIC
winECK5Magic = 0x354B4345 // "ECK5" BCRYPT_ECDH_PUBLIC_P521_MAGIC
winCryptENotFound = 0x80092004 // CRYPT_E_NOT_FOUND
providerMSSoftware = "Microsoft Software Key Storage Provider"
)
var (
winBCryptRSAPublicBlob = winWide("RSAPUBLICBLOB")
winBCryptECCPublicBlob = winWide("ECCPUBLICBLOB")
winNCryptAlgorithmGroupProperty = winWide("Algorithm Group") // NCRYPT_ALGORITHM_GROUP_PROPERTY
winNCryptUniqueNameProperty = winWide("Unique Name") // NCRYPT_UNIQUE_NAME_PROPERTY
winNCryptECCCurveNameProperty = winWide("ECCCurveName") // NCRYPT_ECC_CURVE_NAME_PROPERTY
winCurveIDs = map[uint32]elliptic.Curve{
winECS1Magic: elliptic.P256(), // BCRYPT_ECDSA_PUBLIC_P256_MAGIC
winECS3Magic: elliptic.P384(), // BCRYPT_ECDSA_PUBLIC_P384_MAGIC
winECS5Magic: elliptic.P521(), // BCRYPT_ECDSA_PUBLIC_P521_MAGIC
winECK1Magic: elliptic.P256(), // BCRYPT_ECDH_PUBLIC_P256_MAGIC
winECK3Magic: elliptic.P384(), // BCRYPT_ECDH_PUBLIC_P384_MAGIC
winECK5Magic: elliptic.P521(), // BCRYPT_ECDH_PUBLIC_P521_MAGIC
}
winCurveNames = map[string]elliptic.Curve{
"nistP256": elliptic.P256(), // BCRYPT_ECC_CURVE_NISTP256
"nistP384": elliptic.P384(), // BCRYPT_ECC_CURVE_NISTP384
"nistP521": elliptic.P521(), // BCRYPT_ECC_CURVE_NISTP521
}
winAlgIDs = map[crypto.Hash]*uint16{
crypto.SHA1: winWide("SHA1"), // BCRYPT_SHA1_ALGORITHM
crypto.SHA256: winWide("SHA256"), // BCRYPT_SHA256_ALGORITHM
crypto.SHA384: winWide("SHA384"), // BCRYPT_SHA384_ALGORITHM
crypto.SHA512: winWide("SHA512"), // BCRYPT_SHA512_ALGORITHM
}
// MY is well-known system store on Windows that holds personal certificates. Read
// More about the CA locations here:
// https://learn.microsoft.com/en-us/dotnet/framework/configure-apps/file-schema/wcf/certificate-of-clientcertificate-element?redirectedfrom=MSDN
// https://superuser.com/questions/217719/what-are-the-windows-system-certificate-stores
// https://docs.microsoft.com/en-us/windows/win32/seccrypto/certificate-stores
// https://learn.microsoft.com/en-us/windows/win32/seccrypto/system-store-locations
// https://stackoverflow.com/questions/63286085/which-x509-storename-refers-to-the-certificates-stored-beneath-trusted-root-cert#:~:text=4-,StoreName.,is%20%22Intermediate%20Certification%20Authorities%22.
winMyStore = winWide("MY")
winIntermediateCAStore = winWide("CA")
winRootStore = winWide("Root")
winAuthRootStore = winWide("AuthRoot")
// These DLLs must be available on all Windows hosts
winCrypt32 = windows.MustLoadDLL("crypt32.dll")
winNCrypt = windows.MustLoadDLL("ncrypt.dll")
winCertFindCertificateInStore = winCrypt32.MustFindProc("CertFindCertificateInStore")
winCryptAcquireCertificatePrivateKey = winCrypt32.MustFindProc("CryptAcquireCertificatePrivateKey")
winNCryptExportKey = winNCrypt.MustFindProc("NCryptExportKey")
winNCryptOpenStorageProvider = winNCrypt.MustFindProc("NCryptOpenStorageProvider")
winNCryptGetProperty = winNCrypt.MustFindProc("NCryptGetProperty")
winNCryptSignHash = winNCrypt.MustFindProc("NCryptSignHash")
winFnGetProperty = winGetProperty
)
type winPKCS1PaddingInfo struct {
pszAlgID *uint16
}
type winPSSPaddingInfo struct {
pszAlgID *uint16
cbSalt uint32
}
// createCACertsPool generates a CertPool from the Windows certificate store,
// adding all matching certificates from the caCertsMatch array to the pool.
// All matching certificates (vs first) are added to the pool based on a user
// request. If no certificates are found an error is returned.
func createCACertsPool(cs *winCertStore, storeType uint32, caCertsMatch []string) (*x509.CertPool, error) {
var errs []error
caPool := x509.NewCertPool()
for _, s := range caCertsMatch {
lfs, err := cs.caCertsBySubjectMatch(s, storeType)
if err != nil {
errs = append(errs, err)
} else {
for _, lf := range lfs {
caPool.AddCert(lf)
}
}
}
// If every lookup failed return the errors.
if len(errs) == len(caCertsMatch) {
return nil, fmt.Errorf("unable to match any CA certificate: %v", errs)
}
return caPool, nil
}
// TLSConfig fulfills the same function as reading cert and key pair from
// pem files but sources the Windows certificate store instead. The
// certMatchBy and certMatch fields search the "MY" certificate location
// for the first certificate that matches the certMatch field. The
// caCertsMatch field is used to search the Trusted Root, Third Party Root,
// and Intermediate Certificate Authority locations for certificates with
// Subjects matching the provided strings. If a match is found, the
// certificate is added to the pool that is used to verify the certificate
// chain.
func TLSConfig(certStore StoreType, certMatchBy MatchByType, certMatch string, caCertsMatch []string, config *tls.Config) error {
var (
leaf *x509.Certificate
leafCtx *windows.CertContext
pk *winKey
vOpts = x509.VerifyOptions{}
chains [][]*x509.Certificate
chain []*x509.Certificate
rawChain [][]byte
)
// By StoreType, open a store
if certStore == windowsCurrentUser || certStore == windowsLocalMachine {
var scope uint32
cs, err := winOpenCertStore(providerMSSoftware)
if err != nil || cs == nil {
return err
}
if certStore == windowsCurrentUser {
scope = winCertStoreCurrentUser
}
if certStore == windowsLocalMachine {
scope = winCertStoreLocalMachine
}
// certByIssuer or certBySubject
if certMatchBy == matchBySubject || certMatchBy == MATCHBYEMPTY {
leaf, leafCtx, err = cs.certBySubject(certMatch, scope)
} else if certMatchBy == matchByIssuer {
leaf, leafCtx, err = cs.certByIssuer(certMatch, scope)
} else {
return ErrBadMatchByType
}
if err != nil {
// pass through error from cert search
return err
}
if leaf == nil || leafCtx == nil {
return ErrFailedCertSearch
}
pk, err = cs.certKey(leafCtx)
if err != nil {
return err
}
if pk == nil {
return ErrNoPrivateKeyStoreRef
}
// Look for CA Certificates
if len(caCertsMatch) != 0 {
caPool, err := createCACertsPool(cs, scope, caCertsMatch)
if err != nil {
return err
}
config.ClientCAs = caPool
}
} else {
return ErrBadCertStore
}
// Get intermediates in the cert store for the found leaf IFF there is a full chain of trust in the store
// otherwise just use leaf as the final chain.
//
// Using std lib Verify as a reliable way to get valid chains out of the win store for the leaf; however,
// using empty options since server TLS stanza could be TLS role as server identity or client identity.
chains, err := leaf.Verify(vOpts)
if err != nil || len(chains) == 0 {
chains = append(chains, []*x509.Certificate{leaf})
}
// We have at least one verified chain so pop the first chain and remove the self-signed CA cert (if present)
// from the end of the chain
chain = chains[0]
if len(chain) > 1 {
chain = chain[:len(chain)-1]
}
// For tls.Certificate.Certificate need a [][]byte from []*x509.Certificate
// Approximate capacity for efficiency
rawChain = make([][]byte, 0, len(chain))
for _, link := range chain {
rawChain = append(rawChain, link.Raw)
}
tlsCert := tls.Certificate{
Certificate: rawChain,
PrivateKey: pk,
Leaf: leaf,
}
config.Certificates = []tls.Certificate{tlsCert}
// note: pk is a windows pointer (not freed by Go) but needs to live the life of the server for Signing.
// The cert context (leafCtx) windows pointer must not be freed underneath the pk so also life of the server.
return nil
}
// winWide returns a pointer to uint16 representing the equivalent
// to a Windows LPCWSTR.
func winWide(s string) *uint16 {
w := utf16.Encode([]rune(s))
w = append(w, 0)
return &w[0]
}
// winOpenProvider gets a provider handle for subsequent calls
func winOpenProvider(provider string) (uintptr, error) {
var hProv uintptr
pname := winWide(provider)
// Open the provider, the last parameter is not used
r, _, err := winNCryptOpenStorageProvider.Call(uintptr(unsafe.Pointer(&hProv)), uintptr(unsafe.Pointer(pname)), 0)
if r == 0 {
return hProv, nil
}
return hProv, fmt.Errorf("NCryptOpenStorageProvider returned %X: %v", r, err)
}
// winFindCert wraps the CertFindCertificateInStore library call. Note that any cert context passed
// into prev will be freed. If no certificate was found, nil will be returned.
func winFindCert(store windows.Handle, enc, findFlags, findType uint32, para *uint16, prev *windows.CertContext) (*windows.CertContext, error) {
h, _, err := winCertFindCertificateInStore.Call(
uintptr(store),
uintptr(enc),
uintptr(findFlags),
uintptr(findType),
uintptr(unsafe.Pointer(para)),
uintptr(unsafe.Pointer(prev)),
)
if h == 0 {
// Actual error, or simply not found?
if errno, ok := err.(syscall.Errno); ok && errno == winCryptENotFound {
return nil, ErrFailedCertSearch
}
return nil, ErrFailedCertSearch
}
// nolint:govet
return (*windows.CertContext)(unsafe.Pointer(h)), nil
}
// winCertStore is a store implementation for the Windows Certificate Store
type winCertStore struct {
Prov uintptr
ProvName string
stores map[string]*winStoreHandle
mu sync.Mutex
}
// winOpenCertStore creates a winCertStore
func winOpenCertStore(provider string) (*winCertStore, error) {
cngProv, err := winOpenProvider(provider)
if err != nil {
// pass through error from winOpenProvider
return nil, err
}
wcs := &winCertStore{
Prov: cngProv,
ProvName: provider,
stores: make(map[string]*winStoreHandle),
}
return wcs, nil
}
// winCertContextToX509 creates an x509.Certificate from a Windows cert context.
func winCertContextToX509(ctx *windows.CertContext) (*x509.Certificate, error) {
var der []byte
slice := (*reflect.SliceHeader)(unsafe.Pointer(&der))
slice.Data = uintptr(unsafe.Pointer(ctx.EncodedCert))
slice.Len = int(ctx.Length)
slice.Cap = int(ctx.Length)
return x509.ParseCertificate(der)
}
// certByIssuer matches and returns the first certificate found by passed issuer.
// CertContext pointer returned allows subsequent key operations like Sign. Caller specifies
// current user's personal certs or local machine's personal certs using storeType.
// See CERT_FIND_ISSUER_STR description at https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certfindcertificateinstore
func (w *winCertStore) certByIssuer(issuer string, storeType uint32) (*x509.Certificate, *windows.CertContext, error) {
return w.certSearch(winFindIssuerStr, issuer, winMyStore, storeType)
}
// certBySubject matches and returns the first certificate found by passed subject field.
// CertContext pointer returned allows subsequent key operations like Sign. Caller specifies
// current user's personal certs or local machine's personal certs using storeType.
// See CERT_FIND_SUBJECT_STR description at https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certfindcertificateinstore
func (w *winCertStore) certBySubject(subject string, storeType uint32) (*x509.Certificate, *windows.CertContext, error) {
return w.certSearch(winFindSubjectStr, subject, winMyStore, storeType)
}
// caCertBySubject matches and returns all matching certificates of the subject field.
//
// The following locations are searched:
// 1) Root (Trusted Root Certification Authorities)
// 2) AuthRoot (Third-Party Root Certification Authorities)
// 3) CA (Intermediate Certification Authorities)
//
// Caller specifies current user's personal certs or local machine's personal certs using storeType.
// See CERT_FIND_SUBJECT_STR description at https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certfindcertificateinstore
func (w *winCertStore) caCertsBySubjectMatch(subject string, storeType uint32) ([]*x509.Certificate, error) {
var (
leaf *x509.Certificate
searchLocations = [3]*uint16{winRootStore, winAuthRootStore, winIntermediateCAStore}
rv []*x509.Certificate
)
// surprisingly, an empty string returns a result. We'll treat this as an error.
if subject == "" {
return nil, ErrBadCaCertMatchField
}
for _, sr := range searchLocations {
var err error
if leaf, _, err = w.certSearch(winFindSubjectStr, subject, sr, storeType); err == nil {
rv = append(rv, leaf)
} else {
// Ignore the failed search from a single location. Errors we catch include
// ErrFailedX509Extract (resulting from a malformed certificate) and errors
// around invalid attributes, unsupported algorithms, etc. These are corner
// cases as certificates with these errors shouldn't have been allowed
// to be added to the store in the first place.
if err != ErrFailedCertSearch {
return nil, err
}
}
}
// Not found anywhere
if len(rv) == 0 {
return nil, ErrFailedCertSearch
}
return rv, nil
}
// certSearch is a helper function to lookup certificates based on search type and match value.
// store is used to specify which store to perform the lookup in (system or user).
func (w *winCertStore) certSearch(searchType uint32, matchValue string, searchRoot *uint16, store uint32) (*x509.Certificate, *windows.CertContext, error) {
// store handle to "MY" store
h, err := w.storeHandle(store, searchRoot)
if err != nil {
return nil, nil, err
}
var prev *windows.CertContext
var cert *x509.Certificate
i, err := windows.UTF16PtrFromString(matchValue)
if err != nil {
return nil, nil, ErrFailedCertSearch
}
// pass 0 as the third parameter because it is not used
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa376064(v=vs.85).aspx
nc, err := winFindCert(h, winEncodingX509ASN|winEncodingPKCS7, 0, searchType, i, prev)
if err != nil {
return nil, nil, err
}
if nc != nil {
// certificate found
prev = nc
// Extract the DER-encoded certificate from the cert context
xc, err := winCertContextToX509(nc)
if err == nil {
cert = xc
} else {
return nil, nil, ErrFailedX509Extract
}
} else {
return nil, nil, ErrFailedCertSearch
}
if cert == nil {
return nil, nil, ErrFailedX509Extract
}
return cert, prev, nil
}
type winStoreHandle struct {
handle *windows.Handle
}
func winNewStoreHandle(provider uint32, store *uint16) (*winStoreHandle, error) {
var s winStoreHandle
if s.handle != nil {
return &s, nil
}
st, err := windows.CertOpenStore(
winCertStoreProvSystem,
0,
0,
provider,
uintptr(unsafe.Pointer(store)))
if err != nil {
return nil, ErrBadCryptoStoreProvider
}
s.handle = &st
return &s, nil
}
// winKey implements crypto.Signer and crypto.Decrypter for key based operations.
type winKey struct {
handle uintptr
pub crypto.PublicKey
Container string
AlgorithmGroup string
}
// Public exports a public key to implement crypto.Signer
func (k winKey) Public() crypto.PublicKey {
return k.pub
}
// Sign returns the signature of a hash to implement crypto.Signer
func (k winKey) Sign(_ io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
switch k.AlgorithmGroup {
case "ECDSA", "ECDH":
return winSignECDSA(k.handle, digest)
case "RSA":
hf := opts.HashFunc()
algID, ok := winAlgIDs[hf]
if !ok {
return nil, ErrBadRSAHashAlgorithm
}
switch opts.(type) {
case *rsa.PSSOptions:
return winSignRSAPSSPadding(k.handle, digest, algID)
default:
return winSignRSAPKCS1Padding(k.handle, digest, algID)
}
default:
return nil, ErrBadSigningAlgorithm
}
}
func winSignECDSA(kh uintptr, digest []byte) ([]byte, error) {
var size uint32
// Obtain the size of the signature
r, _, _ := winNCryptSignHash.Call(
kh,
0,
uintptr(unsafe.Pointer(&digest[0])),
uintptr(len(digest)),
0,
0,
uintptr(unsafe.Pointer(&size)),
0)
if r != 0 {
return nil, ErrStoreECDSASigningError
}
// Obtain the signature data
buf := make([]byte, size)
r, _, _ = winNCryptSignHash.Call(
kh,
0,
uintptr(unsafe.Pointer(&digest[0])),
uintptr(len(digest)),
uintptr(unsafe.Pointer(&buf[0])),
uintptr(size),
uintptr(unsafe.Pointer(&size)),
0)
if r != 0 {
return nil, ErrStoreECDSASigningError
}
if len(buf) != int(size) {
return nil, ErrStoreECDSASigningError
}
return winPackECDSASigValue(bytes.NewReader(buf[:size]), len(digest))
}
func winPackECDSASigValue(r io.Reader, digestLength int) ([]byte, error) {
sigR := make([]byte, digestLength)
if _, err := io.ReadFull(r, sigR); err != nil {
return nil, ErrStoreECDSASigningError
}
sigS := make([]byte, digestLength)
if _, err := io.ReadFull(r, sigS); err != nil {
return nil, ErrStoreECDSASigningError
}
var b cryptobyte.Builder
b.AddASN1(asn1.SEQUENCE, func(b *cryptobyte.Builder) {
b.AddASN1BigInt(new(big.Int).SetBytes(sigR))
b.AddASN1BigInt(new(big.Int).SetBytes(sigS))
})
return b.Bytes()
}
func winSignRSAPKCS1Padding(kh uintptr, digest []byte, algID *uint16) ([]byte, error) {
// PKCS#1 v1.5 padding for some TLS 1.2
padInfo := winPKCS1PaddingInfo{pszAlgID: algID}
var size uint32
// Obtain the size of the signature
r, _, _ := winNCryptSignHash.Call(
kh,
uintptr(unsafe.Pointer(&padInfo)),
uintptr(unsafe.Pointer(&digest[0])),
uintptr(len(digest)),
0,
0,
uintptr(unsafe.Pointer(&size)),
winBCryptPadPKCS1)
if r != 0 {
return nil, ErrStoreRSASigningError
}
// Obtain the signature data
sig := make([]byte, size)
r, _, _ = winNCryptSignHash.Call(
kh,
uintptr(unsafe.Pointer(&padInfo)),
uintptr(unsafe.Pointer(&digest[0])),
uintptr(len(digest)),
uintptr(unsafe.Pointer(&sig[0])),
uintptr(size),
uintptr(unsafe.Pointer(&size)),
winBCryptPadPKCS1)
if r != 0 {
return nil, ErrStoreRSASigningError
}
return sig[:size], nil
}
func winSignRSAPSSPadding(kh uintptr, digest []byte, algID *uint16) ([]byte, error) {
// PSS padding for TLS 1.3 and some TLS 1.2
padInfo := winPSSPaddingInfo{pszAlgID: algID, cbSalt: winBCryptPadPSSSalt}
var size uint32
// Obtain the size of the signature
r, _, _ := winNCryptSignHash.Call(
kh,
uintptr(unsafe.Pointer(&padInfo)),
uintptr(unsafe.Pointer(&digest[0])),
uintptr(len(digest)),
0,
0,
uintptr(unsafe.Pointer(&size)),
winBCryptPadPSS)
if r != 0 {
return nil, ErrStoreRSASigningError
}
// Obtain the signature data
sig := make([]byte, size)
r, _, _ = winNCryptSignHash.Call(
kh,
uintptr(unsafe.Pointer(&padInfo)),
uintptr(unsafe.Pointer(&digest[0])),
uintptr(len(digest)),
uintptr(unsafe.Pointer(&sig[0])),
uintptr(size),
uintptr(unsafe.Pointer(&size)),
winBCryptPadPSS)
if r != 0 {
return nil, ErrStoreRSASigningError
}
return sig[:size], nil
}
// certKey wraps CryptAcquireCertificatePrivateKey. It obtains the CNG private
// key of a known certificate and returns a pointer to a winKey which implements
// both crypto.Signer. When a nil cert context is passed
// a nil key is intentionally returned, to model the expected behavior of a
// non-existent cert having no private key.
// https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptacquirecertificateprivatekey
func (w *winCertStore) certKey(cert *windows.CertContext) (*winKey, error) {
// Return early if a nil cert was passed.
if cert == nil {
return nil, nil
}
var (
kh uintptr
spec uint32
mustFree int
)
r, _, _ := winCryptAcquireCertificatePrivateKey.Call(
uintptr(unsafe.Pointer(cert)),
winAcquireCached|winAcquireSilent|winAcquireOnlyNCryptKey,
0, // Reserved, must be null.
uintptr(unsafe.Pointer(&kh)),
uintptr(unsafe.Pointer(&spec)),
uintptr(unsafe.Pointer(&mustFree)),
)
// If the function succeeds, the return value is nonzero (TRUE).
if r == 0 {
return nil, ErrNoPrivateKeyStoreRef
}
if mustFree != 0 {
return nil, ErrNoPrivateKeyStoreRef
}
if spec != winNcryptKeySpec {
return nil, ErrNoPrivateKeyStoreRef
}
return winKeyMetadata(kh)
}
func winKeyMetadata(kh uintptr) (*winKey, error) {
// uc is used to populate the unique container name attribute of the private key
uc, err := winGetPropertyStr(kh, winNCryptUniqueNameProperty)
if err != nil {
// unable to determine key unique name
return nil, ErrExtractingPrivateKeyMetadata
}
alg, err := winGetPropertyStr(kh, winNCryptAlgorithmGroupProperty)
if err != nil {
// unable to determine key algorithm
return nil, ErrExtractingPrivateKeyMetadata
}
var pub crypto.PublicKey
switch alg {
case "ECDSA", "ECDH":
buf, err := winExport(kh, winBCryptECCPublicBlob)
if err != nil {
// failed to export ECC public key
return nil, ErrExtractingECCPublicKey
}
pub, err = unmarshalECC(buf, kh)
if err != nil {
return nil, ErrExtractingECCPublicKey
}
case "RSA":
buf, err := winExport(kh, winBCryptRSAPublicBlob)
if err != nil {
return nil, ErrExtractingRSAPublicKey
}
pub, err = winUnmarshalRSA(buf)
if err != nil {
return nil, ErrExtractingRSAPublicKey
}
default:
return nil, ErrBadPublicKeyAlgorithm
}
return &winKey{handle: kh, pub: pub, Container: uc, AlgorithmGroup: alg}, nil
}
func winGetProperty(kh uintptr, property *uint16) ([]byte, error) {
var strSize uint32
r, _, _ := winNCryptGetProperty.Call(
kh,
uintptr(unsafe.Pointer(property)),
0,
0,
uintptr(unsafe.Pointer(&strSize)),
0,
0)
if r != 0 {
return nil, ErrExtractPropertyFromKey
}
buf := make([]byte, strSize)
r, _, _ = winNCryptGetProperty.Call(
kh,
uintptr(unsafe.Pointer(property)),
uintptr(unsafe.Pointer(&buf[0])),
uintptr(strSize),
uintptr(unsafe.Pointer(&strSize)),
0,
0)
if r != 0 {
return nil, ErrExtractPropertyFromKey
}
return buf, nil
}
func winGetPropertyStr(kh uintptr, property *uint16) (string, error) {
buf, err := winFnGetProperty(kh, property)
if err != nil {
return "", ErrExtractPropertyFromKey
}
uc := bytes.ReplaceAll(buf, []byte{0x00}, []byte(""))
return string(uc), nil
}
func winExport(kh uintptr, blobType *uint16) ([]byte, error) {
var size uint32
// When obtaining the size of a public key, most parameters are not required
r, _, _ := winNCryptExportKey.Call(
kh,
0,
uintptr(unsafe.Pointer(blobType)),
0,
0,
0,
uintptr(unsafe.Pointer(&size)),
0)
if r != 0 {
return nil, ErrExtractingPublicKey
}
// Place the exported key in buf now that we know the size required
buf := make([]byte, size)
r, _, _ = winNCryptExportKey.Call(
kh,
0,
uintptr(unsafe.Pointer(blobType)),
0,
uintptr(unsafe.Pointer(&buf[0])),
uintptr(size),
uintptr(unsafe.Pointer(&size)),
0)
if r != 0 {
return nil, ErrExtractingPublicKey
}
return buf, nil
}
func unmarshalECC(buf []byte, kh uintptr) (*ecdsa.PublicKey, error) {
// BCRYPT_ECCKEY_BLOB from bcrypt.h
header := struct {
Magic uint32
Key uint32
}{}
r := bytes.NewReader(buf)
if err := binary.Read(r, binary.LittleEndian, &header); err != nil {
return nil, ErrExtractingECCPublicKey
}
curve, ok := winCurveIDs[header.Magic]
if !ok {
// Fix for b/185945636, where despite specifying the curve, nCrypt returns
// an incorrect response with BCRYPT_ECDSA_PUBLIC_GENERIC_MAGIC.
var err error
curve, err = winCurveName(kh)
if err != nil {
// unsupported header magic or cannot match the curve by name
return nil, err
}
}
keyX := make([]byte, header.Key)
if n, err := r.Read(keyX); n != int(header.Key) || err != nil {
// failed to read key X
return nil, ErrExtractingECCPublicKey
}
keyY := make([]byte, header.Key)
if n, err := r.Read(keyY); n != int(header.Key) || err != nil {
// failed to read key Y
return nil, ErrExtractingECCPublicKey
}
pub := &ecdsa.PublicKey{
Curve: curve,
X: new(big.Int).SetBytes(keyX),
Y: new(big.Int).SetBytes(keyY),
}
return pub, nil
}
// winCurveName reads the curve name property and returns the corresponding curve.
func winCurveName(kh uintptr) (elliptic.Curve, error) {
cn, err := winGetPropertyStr(kh, winNCryptECCCurveNameProperty)
if err != nil {
// unable to determine the curve property name
return nil, ErrExtractPropertyFromKey
}
curve, ok := winCurveNames[cn]
if !ok {
// unknown curve name
return nil, ErrBadECCCurveName
}
return curve, nil
}
func winUnmarshalRSA(buf []byte) (*rsa.PublicKey, error) {
// BCRYPT_RSA_BLOB from bcrypt.h
header := struct {
Magic uint32
BitLength uint32
PublicExpSize uint32
ModulusSize uint32
UnusedPrime1 uint32
UnusedPrime2 uint32
}{}
r := bytes.NewReader(buf)
if err := binary.Read(r, binary.LittleEndian, &header); err != nil {
return nil, ErrExtractingRSAPublicKey
}
if header.Magic != winRSA1Magic {
// invalid header magic
return nil, ErrExtractingRSAPublicKey
}
if header.PublicExpSize > 8 {
// unsupported public exponent size
return nil, ErrExtractingRSAPublicKey
}
exp := make([]byte, 8)
if n, err := r.Read(exp[8-header.PublicExpSize:]); n != int(header.PublicExpSize) || err != nil {
// failed to read public exponent
return nil, ErrExtractingRSAPublicKey
}
mod := make([]byte, header.ModulusSize)
if n, err := r.Read(mod); n != int(header.ModulusSize) || err != nil {
// failed to read modulus
return nil, ErrExtractingRSAPublicKey
}
pub := &rsa.PublicKey{
N: new(big.Int).SetBytes(mod),
E: int(binary.BigEndian.Uint64(exp)),
}
return pub, nil
}
// storeHandle returns a handle to a given cert store, opening the handle as needed.
func (w *winCertStore) storeHandle(provider uint32, store *uint16) (windows.Handle, error) {
w.mu.Lock()
defer w.mu.Unlock()
key := fmt.Sprintf("%d%s", provider, windows.UTF16PtrToString(store))
var err error
if w.stores[key] == nil {
w.stores[key], err = winNewStoreHandle(provider, store)
if err != nil {
return 0, ErrBadCryptoStoreProvider
}
}
return *w.stores[key].handle, nil
}
// Verify interface conformance.
var _ credential = &winKey{}