forked from google/certtostore
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcerttostore_windows.go
1646 lines (1463 loc) · 54.8 KB
/
certtostore_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
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
//go:build windows
// +build windows
// Copyright 2017 Google Inc.
//
// 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.
package certtostore
import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rsa"
"crypto/x509"
"encoding/binary"
"errors"
"fmt"
"io"
"io/ioutil"
"math/big"
"os"
"os/exec"
"path/filepath"
"reflect"
"strings"
"sync"
"syscall"
"time"
"unicode/utf16"
"unsafe"
"github.com/google/deck"
"golang.org/x/crypto/cryptobyte/asn1"
"golang.org/x/crypto/cryptobyte"
"golang.org/x/sys/windows"
"github.com/hashicorp/go-multierror"
)
// WinCertStorage provides windows-specific additions to the CertStorage interface.
type WinCertStorage interface {
CertStorage
// Remove removes certificates issued by any of w.issuers from the user and/or system cert stores.
// If it is unable to remove any certificates, it returns an error.
Remove(removeSystem bool) error
// Link will associate the certificate installed in the system store to the user store.
Link() error
// Close frees the handle to the certificate provider, the certificate store, etc.
Close() error
// CertWithContext performs a certificate lookup using value of issuers that
// was provided when WinCertStore was created. It returns both the certificate
// and its Windows context, which can be used to perform other operations,
// such as looking up the private key with CertKey().
//
// You must call FreeCertContext on the context after use.
CertWithContext() (*x509.Certificate, *windows.CertContext, error)
// CertKey wraps CryptAcquireCertificatePrivateKey. It obtains the CNG private
// key of a known certificate and returns a pointer to a Key which implements
// both crypto.Signer and crypto.Decrypter. 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
CertKey(cert *windows.CertContext) (*Key, error)
// StoreWithDisposition imports certificates into the Windows certificate store.
// disposition specifies the action to take if a matching certificate
// or a link to a matching certificate already exists in the store
// https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certaddcertificatecontexttostore
StoreWithDisposition(cert *x509.Certificate, intermediate *x509.Certificate, disposition uint32) error
}
const (
// wincrypt.h constants
acquireCached = 0x1 // CRYPT_ACQUIRE_CACHE_FLAG
acquireSilent = 0x40 // CRYPT_ACQUIRE_SILENT_FLAG
acquireOnlyNCryptKey = 0x40000 // CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG
encodingX509ASN = 1 // X509_ASN_ENCODING
encodingPKCS7 = 65536 // PKCS_7_ASN_ENCODING
certStoreProvSystem = 10 // CERT_STORE_PROV_SYSTEM
certStoreCurrentUser = uint32(certStoreCurrentUserID << compareShift) // CERT_SYSTEM_STORE_CURRENT_USER
certStoreLocalMachine = uint32(certStoreLocalMachineID << compareShift) // CERT_SYSTEM_STORE_LOCAL_MACHINE
certStoreCurrentUserID = 1 // CERT_SYSTEM_STORE_CURRENT_USER_ID
certStoreLocalMachineID = 2 // CERT_SYSTEM_STORE_LOCAL_MACHINE_ID
infoIssuerFlag = 4 // CERT_INFO_ISSUER_FLAG
compareNameStrW = 8 // CERT_COMPARE_NAME_STR_A
compareShift = 16 // CERT_COMPARE_SHIFT
findIssuerStr = compareNameStrW<<compareShift | infoIssuerFlag // CERT_FIND_ISSUER_STR_W
signatureKeyUsage = 0x80 // CERT_DIGITAL_SIGNATURE_KEY_USAGE
ncryptKeySpec = 0xFFFFFFFF // CERT_NCRYPT_KEY_SPEC
// Legacy CryptoAPI flags
bCryptPadPKCS1 uintptr = 0x2
bCryptPadPSS uintptr = 0x8
// Magic numbers for public key blobs.
rsa1Magic = 0x31415352 // "RSA1" BCRYPT_RSAPUBLIC_MAGIC
ecdsaP256Magic = 0x31534345 // BCRYPT_ECDSA_PUBLIC_P256_MAGIC
ecdsaP384Magic = 0x33534345 // BCRYPT_ECDSA_PUBLIC_P384_MAGIC
ecdsaP521Magic = 0x35534345 // BCRYPT_ECDSA_PUBLIC_P521_MAGIC
ecdhP256Magic = 0x314B4345 // BCRYPT_ECDH_PUBLIC_P256_MAGIC
ecdhP384Magic = 0x334B4345 // BCRYPT_ECDH_PUBLIC_P384_MAGIC
ecdhP521Magic = 0x354B4345 // BCRYPT_ECDH_PUBLIC_P521_MAGIC
// ncrypt.h constants
ncryptPersistFlag = 0x80000000 // NCRYPT_PERSIST_FLAG
ncryptAllowDecryptFlag = 0x1 // NCRYPT_ALLOW_DECRYPT_FLAG
ncryptAllowSigningFlag = 0x2 // NCRYPT_ALLOW_SIGNING_FLAG
ncryptWriteKeyToLegacyStore = 0x00000200 // NCRYPT_WRITE_KEY_TO_LEGACY_STORE_FLAG
// NCryptPadOAEPFlag is used with Decrypt to specify whether to use OAEP.
NCryptPadOAEPFlag = 0x00000004 // NCRYPT_PAD_OAEP_FLAG
// key creation flags.
nCryptMachineKey = 0x20 // NCRYPT_MACHINE_KEY_FLAG
nCryptOverwriteKey = 0x80 // NCRYPT_OVERWRITE_KEY_FLAG
// winerror.h constants
cryptENotFound = 0x80092004 // CRYPT_E_NOT_FOUND
// ProviderMSPlatform represents the Microsoft Platform Crypto Provider
ProviderMSPlatform = "Microsoft Platform Crypto Provider"
// ProviderMSSoftware represents the Microsoft Software Key Storage Provider
ProviderMSSoftware = "Microsoft Software Key Storage Provider"
// ProviderMSLegacy represents the CryptoAPI compatible Enhanced Cryptographic Provider
ProviderMSLegacy = "Microsoft Enhanced Cryptographic Provider v1.0"
// Chain resolution constants
hcceLocalMachine = windows.Handle(0x01) // HCCE_LOCAL_MACHINE
certChainCacheOnlyURLRetrieval = 0x00000004 // CERT_CHAIN_CACHE_ONLY_URL_RETRIEVAL
certChainDisableAIA = 0x00002000 // CERT_CHAIN_DISABLE_AIA
certChainRevocationCheckCacheOnly = 0x80000000 // CERT_CHAIN_REVOCATION_CHECK_CACHE_ONLY
)
var (
// Key blob type constants.
bCryptRSAPublicBlob = wide("RSAPUBLICBLOB")
bCryptECCPublicBlob = wide("ECCPUBLICBLOB")
// Key storage properties
nCryptAlgorithmGroupProperty = wide("Algorithm Group") // NCRYPT_ALGORITHM_GROUP_PROPERTY
nCryptUniqueNameProperty = wide("Unique Name") // NCRYPT_UNIQUE_NAME_PROPERTY
nCryptECCCurveNameProperty = wide("ECCCurveName") // NCRYPT_ECC_CURVE_NAME_PROPERTY
nCryptImplTypeProperty = wide("Impl Type") // NCRYPT_IMPL_TYPE_PROPERTY
nCryptProviderHandleProperty = wide("Provider Handle") // NCRYPT_PROV_HANDLE
// Flags for NCRYPT_IMPL_TYPE_PROPERTY
nCryptImplHardwareFlag = 0x00000001 // NCRYPT_IMPL_HARDWARE_FLAG
nCryptImplSoftwareFlag = 0x00000002 // NCRYPT_IMPL_SOFTWARE_FLAG
nCryptImplRemovableFlag = 0x00000008 // NCRYPT_IMPL_REMOVABLE_FLAG
nCryptImplHardwareRngFlag = 0x00000010 // NCRYPT_IMPL_HARDWARE_RNG_FLAG
// curveIDs maps bcrypt key blob magic numbers to elliptic curves.
curveIDs = map[uint32]elliptic.Curve{
ecdsaP256Magic: elliptic.P256(), // BCRYPT_ECDSA_PUBLIC_P256_MAGIC
ecdsaP384Magic: elliptic.P384(), // BCRYPT_ECDSA_PUBLIC_P384_MAGIC
ecdsaP521Magic: elliptic.P521(), // BCRYPT_ECDSA_PUBLIC_P521_MAGIC
ecdhP256Magic: elliptic.P256(), // BCRYPT_ECDH_PUBLIC_P256_MAGIC
ecdhP384Magic: elliptic.P384(), // BCRYPT_ECDH_PUBLIC_P384_MAGIC
ecdhP521Magic: elliptic.P521(), // BCRYPT_ECDH_PUBLIC_P521_MAGIC
}
// curveNames maps bcrypt curve names to elliptic curves. We use it
// for a fallback mechanism when magic is incorrect (see b/185945636).
curveNames = 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
}
// algIDs maps crypto.Hash values to bcrypt.h constants.
algIDs = map[crypto.Hash]*uint16{
crypto.SHA1: wide("SHA1"), // BCRYPT_SHA1_ALGORITHM
crypto.SHA256: wide("SHA256"), // BCRYPT_SHA256_ALGORITHM
crypto.SHA384: wide("SHA384"), // BCRYPT_SHA384_ALGORITHM
crypto.SHA512: wide("SHA512"), // BCRYPT_SHA512_ALGORITHM
}
// MY, CA and ROOT are well-known system stores that holds certificates.
// The store that is opened (system or user) depends on the system call used.
// see https://msdn.microsoft.com/en-us/library/windows/desktop/aa376560(v=vs.85).aspx)
my = wide("MY")
ca = wide("CA")
root = wide("ROOT")
crypt32 = windows.MustLoadDLL("crypt32.dll")
nCrypt = windows.MustLoadDLL("ncrypt.dll")
certDeleteCertificateFromStore = crypt32.MustFindProc("CertDeleteCertificateFromStore")
certFindCertificateInStore = crypt32.MustFindProc("CertFindCertificateInStore")
certFreeCertificateChain = crypt32.MustFindProc("CertFreeCertificateChain")
certGetCertificateChain = crypt32.MustFindProc("CertGetCertificateChain")
certGetIntendedKeyUsage = crypt32.MustFindProc("CertGetIntendedKeyUsage")
cryptAcquireCertificatePrivateKey = crypt32.MustFindProc("CryptAcquireCertificatePrivateKey")
cryptFindCertificateKeyProvInfo = crypt32.MustFindProc("CryptFindCertificateKeyProvInfo")
nCryptCreatePersistedKey = nCrypt.MustFindProc("NCryptCreatePersistedKey")
nCryptDecrypt = nCrypt.MustFindProc("NCryptDecrypt")
nCryptExportKey = nCrypt.MustFindProc("NCryptExportKey")
nCryptFinalizeKey = nCrypt.MustFindProc("NCryptFinalizeKey")
nCryptFreeObject = nCrypt.MustFindProc("NCryptFreeObject")
nCryptOpenKey = nCrypt.MustFindProc("NCryptOpenKey")
nCryptOpenStorageProvider = nCrypt.MustFindProc("NCryptOpenStorageProvider")
nCryptGetProperty = nCrypt.MustFindProc("NCryptGetProperty")
nCryptSetProperty = nCrypt.MustFindProc("NCryptSetProperty")
nCryptSignHash = nCrypt.MustFindProc("NCryptSignHash")
// Path to icacls binary
icaclsPath = filepath.Join(os.Getenv("SystemRoot"), "System32", "icacls.exe")
// Test helpers
fnGetProperty = getProperty
)
// pkcs1PaddingInfo is the BCRYPT_PKCS1_PADDING_INFO struct in bcrypt.h.
type pkcs1PaddingInfo struct {
pszAlgID *uint16
}
// pssPaddingInfo is the BCRYPT_PSS_PADDING_INFO struct in bcrypt.h.
type pssPaddingInfo struct {
pszAlgID *uint16
cbSalt uint32
}
// wide returns a pointer to a a uint16 representing the equivalent
// to a Windows LPCWSTR.
func wide(s string) *uint16 {
w := utf16.Encode([]rune(s))
w = append(w, 0)
return &w[0]
}
func openProvider(provider string) (uintptr, error) {
var hProv uintptr
pname := wide(provider)
// Open the provider, the last parameter is not used
r, _, err := nCryptOpenStorageProvider.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)
}
// findCert wraps the CertFindCertificateInStore call. Note that any cert context passed
// into prev will be freed. If no certificate was found, nil will be returned.
func findCert(store windows.Handle, enc, findFlags, findType uint32, para *uint16, prev *windows.CertContext) (*windows.CertContext, error) {
h, _, err := certFindCertificateInStore.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 == cryptENotFound {
return nil, nil
}
return nil, err
}
return (*windows.CertContext)(unsafe.Pointer(h)), nil
}
// FreeCertContext frees a certificate context after use.
func FreeCertContext(ctx *windows.CertContext) error {
return windows.CertFreeCertificateContext(ctx)
}
// intendedKeyUsage wraps CertGetIntendedKeyUsage. If there are key usage bytes they will be returned,
// otherwise 0 will be returned. The final parameter (2) represents the size in bytes of &usage.
func intendedKeyUsage(enc uint32, cert *windows.CertContext) (usage uint16) {
certGetIntendedKeyUsage.Call(uintptr(enc), uintptr(unsafe.Pointer(cert.CertInfo)), uintptr(unsafe.Pointer(&usage)), 2)
return
}
// WinCertStore is a CertStorage implementation for the Windows Certificate Store.
type WinCertStore struct {
Prov uintptr
ProvName string
issuers []string
intermediateIssuers []string
container string
keyStorageFlags uintptr
certChains [][]*x509.Certificate
stores map[string]*storeHandle
keyAccessFlags uintptr
mu sync.Mutex
}
// OpenWinCertStore creates a WinCertStore with keys accessible by all users on a machine.
// Call Close() when finished using the store.
func OpenWinCertStore(provider, container string, issuers, intermediateIssuers []string, legacyKey bool) (*WinCertStore, error) {
return openWinCertStore(provider, container, issuers, intermediateIssuers, legacyKey, false)
}
// OpenWinCertStoreCurrentUser creates a WinCertStore with keys accessible by current user.
// Call Close() when finished using the store.
func OpenWinCertStoreCurrentUser(provider, container string, issuers, intermediateIssuers []string, legacyKey bool) (*WinCertStore, error) {
return openWinCertStore(provider, container, issuers, intermediateIssuers, legacyKey, true)
}
func openWinCertStore(provider, container string, issuers, intermediateIssuers []string, legacyKey, currentUser bool) (*WinCertStore, error) {
// Open a handle to the crypto provider we will use for private key operations
cngProv, err := openProvider(provider)
if err != nil {
return nil, fmt.Errorf("unable to open crypto provider or provider not available: %v", err)
}
wcs := &WinCertStore{
Prov: cngProv,
ProvName: provider,
issuers: issuers,
intermediateIssuers: intermediateIssuers,
container: container,
stores: make(map[string]*storeHandle),
}
if legacyKey {
wcs.keyStorageFlags = ncryptWriteKeyToLegacyStore
wcs.ProvName = ProviderMSLegacy
}
if !currentUser {
wcs.keyAccessFlags = nCryptMachineKey
}
return wcs, nil
}
// certContextToX509 creates an x509.Certificate from a Windows cert context.
func certContextToX509(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(append([]byte{}, der...))
}
// extractSimpleChain extracts the requested certificate chain from a CertSimpleChain.
// Adapted from crypto.x509.root_windows
func extractSimpleChain(simpleChain **windows.CertSimpleChain, chainCount, chainIndex int) ([]*x509.Certificate, error) {
if simpleChain == nil || chainCount == 0 || chainIndex >= chainCount {
return nil, errors.New("invalid simple chain")
}
// Convert the simpleChain array to a huge slice and slice it to the length we want.
// https://github.com/golang/go/wiki/cgo#turning-c-arrays-into-go-slices
simpleChains := (*[1 << 20]*windows.CertSimpleChain)(unsafe.Pointer(simpleChain))[:chainCount:chainCount]
// Each simple chain contains the chain of certificates, summary trust information
// about the chain, and trust information about each certificate element in the chain.
lastChain := simpleChains[chainIndex]
chainLen := int(lastChain.NumElements)
elements := (*[1 << 20]*windows.CertChainElement)(unsafe.Pointer(lastChain.Elements))[:chainLen:chainLen]
chain := make([]*x509.Certificate, 0, chainLen)
for _, element := range elements {
xc, err := certContextToX509(element.CertContext)
if err != nil {
return nil, err
}
chain = append(chain, xc)
}
return chain, nil
}
func (w *WinCertStore) storeDomain() uint32 {
if w.keyAccessFlags == nCryptMachineKey {
return certStoreLocalMachine
}
return certStoreCurrentUser
}
// resolveCertChains builds chains to roots from a given certificate using the local machine store.
func (w *WinCertStore) resolveChains(cert *windows.CertContext) error {
var (
chainPara windows.CertChainPara
chainCtx *windows.CertChainContext
)
// Search the system for candidate certificate chains.
chainPara.Size = uint32(unsafe.Sizeof(chainPara))
success, _, err := certGetCertificateChain.Call(
uintptr(unsafe.Pointer(hcceLocalMachine)),
uintptr(unsafe.Pointer(cert)),
uintptr(unsafe.Pointer(nil)), // Use current system time as validation time.
uintptr(cert.Store),
uintptr(unsafe.Pointer(&chainPara)),
certChainRevocationCheckCacheOnly|certChainCacheOnlyURLRetrieval|certChainDisableAIA,
uintptr(unsafe.Pointer(nil)), // Reserved.
uintptr(unsafe.Pointer(&chainCtx)),
)
if success == 0 {
return fmt.Errorf("certGetCertificateChain: %v", err)
}
defer certFreeCertificateChain.Call(uintptr(unsafe.Pointer(chainCtx)))
chainCount := int(chainCtx.ChainCount)
certChains := make([][]*x509.Certificate, 0, chainCount)
for i := 0; i < chainCount; i++ {
x509Certs, err := extractSimpleChain(chainCtx.Chains, chainCount, i)
if err != nil {
return fmt.Errorf("extractSimpleChain: %v", err)
}
certChains = append(certChains, x509Certs)
}
return nil
}
// Cert returns the current cert associated with this WinCertStore or nil if there isn't one.
func (w *WinCertStore) Cert() (*x509.Certificate, error) {
c, ctx, err := w.CertWithContext()
if err != nil {
return nil, err
}
FreeCertContext(ctx)
return c, nil
}
// CertWithContext performs a certificate lookup using value of issuers that
// was provided when WinCertStore was created. It returns both the certificate
// and its Windows context, which can be used to perform other operations,
// such as looking up the private key with CertKey().
//
// You must call FreeCertContext on the context after use.
func (w *WinCertStore) CertWithContext() (*x509.Certificate, *windows.CertContext, error) {
c, ctx, err := w.cert(w.issuers, my, w.storeDomain())
if err != nil {
return nil, nil, err
}
// If no cert was returned, skip resolving chains and return.
if c == nil {
return nil, nil, nil
}
if err := w.resolveChains(ctx); err != nil {
return nil, nil, err
}
return c, ctx, nil
}
// cert is a helper function to lookup certificates based on a known issuer.
// store is used to specify which store to perform the lookup in (system or user).
func (w *WinCertStore) cert(issuers []string, searchRoot *uint16, store uint32) (*x509.Certificate, *windows.CertContext, error) {
h, err := w.storeHandle(store, searchRoot)
if err != nil {
return nil, nil, err
}
var prev *windows.CertContext
var cert *x509.Certificate
for _, issuer := range issuers {
i, err := windows.UTF16PtrFromString(issuer)
if err != nil {
return nil, nil, err
}
// 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 := findCert(h, encodingX509ASN|encodingPKCS7, 0, findIssuerStr, i, prev)
if err != nil {
return nil, nil, fmt.Errorf("finding certificates: %v", err)
}
if nc == nil {
// No certificate found
continue
}
prev = nc
if (intendedKeyUsage(encodingX509ASN, nc) & signatureKeyUsage) == 0 {
continue
}
// Extract the DER-encoded certificate from the cert context.
xc, err := certContextToX509(nc)
if err != nil {
continue
}
cert = xc
break
}
if cert == nil {
return nil, nil, nil
}
return cert, prev, nil
}
func freeObject(h uintptr) error {
r, _, err := nCryptFreeObject.Call(h)
if r == 0 {
return nil
}
return fmt.Errorf("NCryptFreeObject returned %X: %v", r, err)
}
// Close frees the handle to the certificate provider, the certificate store, etc.
func (w *WinCertStore) Close() error {
var result error
for _, v := range w.stores {
if v != nil {
if err := v.Close(); err != nil {
multierror.Append(result, err)
}
}
}
if err := freeObject(w.Prov); err != nil {
multierror.Append(result, err)
}
return result
}
// Link will associate the certificate installed in the system store to the user store.
func (w *WinCertStore) Link() error {
cert, _, err := w.cert(w.issuers, my, certStoreLocalMachine)
if err != nil {
return fmt.Errorf("checking for existing machine certificates returned: %v", err)
}
if cert == nil {
return nil
}
// If the user cert is already there and matches the system cert, return early.
userCert, _, err := w.cert(w.issuers, my, certStoreCurrentUser)
if err != nil {
return fmt.Errorf("checking for existing user certificates returned: %v", err)
}
if userCert != nil {
if cert.SerialNumber.Cmp(userCert.SerialNumber) == 0 {
fmt.Fprintf(os.Stdout, "Certificate %s is already linked to the user certificate store.\n", cert.SerialNumber)
return nil
}
}
// The user context is missing the cert, or it doesn't match, so proceed with the link.
certContext, err := windows.CertCreateCertificateContext(
encodingX509ASN|encodingPKCS7,
&cert.Raw[0],
uint32(len(cert.Raw)))
if err != nil {
return fmt.Errorf("CertCreateCertificateContext returned: %v", err)
}
defer windows.CertFreeCertificateContext(certContext)
// Associate the private key we previously generated
r, _, err := cryptFindCertificateKeyProvInfo.Call(
uintptr(unsafe.Pointer(certContext)),
uintptr(uint32(0)),
0,
)
// Windows calls will fill err with a success message, r is what must be checked instead
if r == 0 {
fmt.Printf("found a matching private key for the certificate, but association failed: %v", err)
}
h, err := w.storeHandle(certStoreCurrentUser, my)
if err != nil {
return err
}
// Add the cert context to the users certificate store
if err := windows.CertAddCertificateContextToStore(h, certContext, windows.CERT_STORE_ADD_ALWAYS, nil); err != nil {
return fmt.Errorf("CertAddCertificateContextToStore returned: %v", err)
}
deck.Infof("Successfully linked to existing system certificate with serial %s.", cert.SerialNumber)
fmt.Fprintf(os.Stdout, "Successfully linked to existing system certificate with serial %s.\n", cert.SerialNumber)
// Link legacy crypto only if requested.
if w.ProvName == ProviderMSLegacy {
return w.linkLegacy()
}
return nil
}
type storeHandle struct {
handle *windows.Handle
}
func newStoreHandle(provider uint32, store *uint16) (*storeHandle, error) {
var s storeHandle
if s.handle != nil {
return &s, nil
}
st, err := windows.CertOpenStore(
certStoreProvSystem,
0,
0,
provider,
uintptr(unsafe.Pointer(store)))
if err != nil {
return nil, fmt.Errorf("CertOpenStore for the user store returned: %v", err)
}
s.handle = &st
return &s, nil
}
func (s *storeHandle) Close() error {
if s.handle != nil {
return windows.CertCloseStore(*s.handle, 1)
}
return nil
}
// linkLegacy will associate the private key for a system certificate backed by cryptoAPI to
// the copy of the certificate stored in the user store. This makes the key available to legacy
// applications which may require it be specifically present in the users store to be read.
func (w *WinCertStore) linkLegacy() error {
if w.ProvName != ProviderMSLegacy {
return fmt.Errorf("cannot link legacy key, Provider mismatch: got %q, want %q", w.ProvName, ProviderMSLegacy)
}
deck.Info("Linking legacy key to the user private store.")
cert, context, err := w.cert(w.issuers, my, certStoreLocalMachine)
if err != nil {
return fmt.Errorf("cert lookup returned: %v", err)
}
if context == nil {
return errors.New("cert lookup returned: nil")
}
// Lookup the private key for the certificate.
k, err := w.CertKey(context)
if err != nil {
return fmt.Errorf("unable to find legacy private key for %s: %v", cert.SerialNumber, err)
}
if k == nil {
return errors.New("private key lookup returned: nil")
}
if k.LegacyContainer == "" {
return fmt.Errorf("unable to find legacy private key for %s: container was empty", cert.SerialNumber)
}
// Generate the path to the expected current user's private key file.
sid, err := UserSID()
if err != nil {
return fmt.Errorf("unable to determine user SID: %v", err)
}
_, file := filepath.Split(k.LegacyContainer)
userContainer := fmt.Sprintf(`%s\Microsoft\Crypto\RSA\%s\%s`, os.Getenv("AppData"), sid, file)
// Link the private key to the users private key store.
if err := copyFile(k.LegacyContainer, userContainer); err != nil {
return err
}
deck.Infof("Legacy key %q was located and linked to the user store.", k.LegacyContainer)
return nil
}
// Remove removes certificates issued by any of w.issuers from the user and/or system cert stores.
// If it is unable to remove any certificates, it returns an error.
func (w *WinCertStore) Remove(removeSystem bool) error {
for _, issuer := range w.issuers {
if err := w.remove(issuer, removeSystem); err != nil {
return err
}
}
return nil
}
// remove removes a certificate issued by w.issuer from the user and/or system cert stores.
func (w *WinCertStore) remove(issuer string, removeSystem bool) error {
h, err := w.storeHandle(certStoreCurrentUser, my)
if err != nil {
return err
}
userCertContext, err := findCert(
h,
encodingX509ASN|encodingPKCS7,
0,
findIssuerStr,
wide(issuer),
nil)
if err != nil {
return fmt.Errorf("remove: finding user certificate issued by %s failed: %v", issuer, err)
}
if userCertContext != nil {
if err := RemoveCertByContext(userCertContext); err != nil {
return fmt.Errorf("failed to remove user cert: %v", err)
}
deck.Info("Cleaned up a user certificate.")
fmt.Fprintln(os.Stderr, "Cleaned up a user certificate.")
}
// if we're only removing the user cert, return early.
if !removeSystem {
return nil
}
h2, err := w.storeHandle(certStoreLocalMachine, my)
if err != nil {
return err
}
systemCertContext, err := findCert(
h2,
encodingX509ASN|encodingPKCS7,
0,
findIssuerStr,
wide(issuer),
nil)
if err != nil {
return fmt.Errorf("remove: finding system certificate issued by %s failed: %v", issuer, err)
}
if systemCertContext != nil {
if err := RemoveCertByContext(systemCertContext); err != nil {
return fmt.Errorf("failed to remove system cert: %v", err)
}
deck.Info("Cleaned up a system certificate.")
fmt.Fprintln(os.Stderr, "Cleaned up a system certificate.")
}
return nil
}
// RemoveCertByContext wraps CertDeleteCertificateFromStore. If the call succeeds, nil is returned, otherwise
// the extended error is returned.
func RemoveCertByContext(certContext *windows.CertContext) error {
r, _, err := certDeleteCertificateFromStore.Call(uintptr(unsafe.Pointer(certContext)))
if r != 1 {
return fmt.Errorf("certdeletecertificatefromstore failed with %X: %v", r, err)
}
return nil
}
// Intermediate returns the current intermediate cert associated with this
// WinCertStore or nil if there isn't one.
func (w *WinCertStore) Intermediate() (*x509.Certificate, error) {
c, _, err := w.cert(w.intermediateIssuers, my, w.storeDomain())
return c, err
}
// Root returns the certificate issued by the specified issuer from the
// root certificate store 'ROOT/Certificates'.
func (w *WinCertStore) Root(issuer []string) (*x509.Certificate, error) {
c, _, err := w.cert(issuer, root, w.storeDomain())
return c, err
}
// CertificateChain returns the leaf and subsequent certificates.
func (w *WinCertStore) CertificateChain() ([][]*x509.Certificate, error) {
// TODO: Once https://github.com/golang/go/issues/34977 is resolved
// use certificateChain() instead.
cert, err := w.Cert()
if err != nil {
return nil, fmt.Errorf("unable to load leaf: %v", err)
}
if cert == nil {
return nil, fmt.Errorf("load leaf: no certificate found")
}
// Calling Cert() builds certChains.
return w.certChains, nil
}
// Key implements crypto.Signer and crypto.Decrypter for key based operations.
type Key struct {
handle uintptr
pub crypto.PublicKey
Container string
LegacyContainer string
AlgorithmGroup string
}
// Public exports a public key to implement crypto.Signer
func (k Key) Public() crypto.PublicKey {
return k.pub
}
// Sign returns the signature of a hash to implement crypto.Signer
func (k Key) Sign(_ io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
switch k.AlgorithmGroup {
case "ECDSA", "ECDH":
return signECDSA(k.handle, digest)
case "RSA":
return signRSA(k.handle, digest, opts)
default:
return nil, fmt.Errorf("unsupported algorithm group %v", k.AlgorithmGroup)
}
}
// TransientTpmHandle returns the key's underlying transient TPM handle.
func (k Key) TransientTpmHandle() uintptr {
return k.handle
}
func signECDSA(kh uintptr, digest []byte) ([]byte, error) {
var size uint32
// Obtain the size of the signature
r, _, err := nCryptSignHash.Call(
kh,
0,
uintptr(unsafe.Pointer(&digest[0])),
uintptr(len(digest)),
0,
0,
uintptr(unsafe.Pointer(&size)),
0)
if r != 0 {
return nil, fmt.Errorf("NCryptSignHash returned %X during size check: %v", r, err)
}
// Obtain the signature data
buf := make([]byte, size)
r, _, err = nCryptSignHash.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, fmt.Errorf("NCryptSignHash returned %X during signing: %v", r, err)
}
if len(buf) != int(size) {
return nil, errors.New("invalid length")
}
return packECDSASigValue(bytes.NewReader(buf[:size]), len(digest))
}
func packECDSASigValue(r io.Reader, digestLength int) ([]byte, error) {
sigR := make([]byte, digestLength)
if _, err := io.ReadFull(r, sigR); err != nil {
return nil, fmt.Errorf("failed to read R: %v", err)
}
sigS := make([]byte, digestLength)
if _, err := io.ReadFull(r, sigS); err != nil {
return nil, fmt.Errorf("failed to read S: %v", err)
}
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 signRSA(kh uintptr, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
paddingInfo, flags, err := rsaPadding(opts)
if err != nil {
return nil, fmt.Errorf("failed to construct padding info: %v", err)
}
var size uint32
// Obtain the size of the signature
r, _, err := nCryptSignHash.Call(
kh,
uintptr(paddingInfo),
uintptr(unsafe.Pointer(&digest[0])),
uintptr(len(digest)),
0,
0,
uintptr(unsafe.Pointer(&size)),
flags)
if r != 0 {
return nil, fmt.Errorf("NCryptSignHash returned %X during size check: %v", r, err)
}
// Obtain the signature data
sig := make([]byte, size)
r, _, err = nCryptSignHash.Call(
kh,
uintptr(paddingInfo),
uintptr(unsafe.Pointer(&digest[0])),
uintptr(len(digest)),
uintptr(unsafe.Pointer(&sig[0])),
uintptr(size),
uintptr(unsafe.Pointer(&size)),
flags)
if r != 0 {
return nil, fmt.Errorf("NCryptSignHash returned %X during signing: %v", r, err)
}
return sig[:size], nil
}
// rsaPadding constructs the padding info structure and flags from the crypto.SignerOpts.
// https://learn.microsoft.com/en-us/windows/win32/api/bcrypt/nf-bcrypt-bcryptsignhash
func rsaPadding(opts crypto.SignerOpts) (unsafe.Pointer, uintptr, error) {
algID, ok := algIDs[opts.HashFunc()]
if !ok {
return nil, 0, fmt.Errorf("unsupported RSA hash algorithm %v", opts.HashFunc())
}
if o, ok := opts.(*rsa.PSSOptions); ok {
saltLength := o.SaltLength
switch saltLength {
case rsa.PSSSaltLengthAuto:
return nil, 0, fmt.Errorf("rsa.PSSSaltLengthAuto is not supported")
case rsa.PSSSaltLengthEqualsHash:
saltLength = o.HashFunc().Size()
}
return unsafe.Pointer(&pssPaddingInfo{
pszAlgID: algID,
cbSalt: uint32(saltLength),
}), bCryptPadPSS, nil
}
return unsafe.Pointer(&pkcs1PaddingInfo{
pszAlgID: algID,
}), bCryptPadPKCS1, nil
}
// DecrypterOpts implements crypto.DecrypterOpts and contains the
// flags required for the NCryptDecrypt system call.
type DecrypterOpts struct {
// Hashfunc represents the hashing function that was used during
// encryption and is mapped to the Microsoft equivalent LPCWSTR.
Hashfunc crypto.Hash
// Flags represents the dwFlags parameter for NCryptDecrypt
Flags uint32
}
// oaepPaddingInfo is the BCRYPT_OAEP_PADDING_INFO struct in bcrypt.h.
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa375526(v=vs.85).aspx
type oaepPaddingInfo struct {
pszAlgID *uint16 // pszAlgId
pbLabel *uint16 // pbLabel
cbLabel uint32 // cbLabel
}
// Decrypt returns the decrypted contents of the encrypted blob, and implements
// crypto.Decrypter for Key.
func (k Key) Decrypt(rand io.Reader, blob []byte, opts crypto.DecrypterOpts) ([]byte, error) {
decrypterOpts, ok := opts.(DecrypterOpts)
if !ok {
return nil, errors.New("opts was not certtostore.DecrypterOpts")
}
algID, ok := algIDs[decrypterOpts.Hashfunc]
if !ok {
return nil, fmt.Errorf("unsupported hash algorithm %v", decrypterOpts.Hashfunc)
}
padding := oaepPaddingInfo{
pszAlgID: algID,
pbLabel: wide(""),
cbLabel: 0,
}
return decrypt(k.handle, blob, padding, decrypterOpts.Flags)
}
// decrypt wraps the NCryptDecrypt function and returns the decrypted bytes
// that were previously encrypted by NCryptEncrypt or another compatible
// function such as rsa.EncryptOAEP.
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa376249(v=vs.85).aspx
func decrypt(kh uintptr, blob []byte, padding oaepPaddingInfo, flags uint32) ([]byte, error) {
var size uint32
// Obtain the size of the decrypted data
r, _, err := nCryptDecrypt.Call(
kh,
uintptr(unsafe.Pointer(&blob[0])),
uintptr(len(blob)),
uintptr(unsafe.Pointer(&padding)),
0, // Must be null on first run.
0, // Ignored on first run.
uintptr(unsafe.Pointer(&size)),
uintptr(flags))
if r != 0 {
return nil, fmt.Errorf("NCryptDecrypt returned %X during size check: %v", r, err)
}
// Decrypt the message
plainText := make([]byte, size)
r, _, err = nCryptDecrypt.Call(
kh,
uintptr(unsafe.Pointer(&blob[0])),
uintptr(len(blob)),
uintptr(unsafe.Pointer(&padding)),
uintptr(unsafe.Pointer(&plainText[0])),
uintptr(size),
uintptr(unsafe.Pointer(&size)),
uintptr(flags))
if r != 0 {
return nil, fmt.Errorf("NCryptDecrypt returned %X during decryption: %v", r, err)
}
return plainText[:size], nil
}