mirror of
https://github.com/gogrlx/nats-server.git
synced 2026-04-02 03:38:42 -07:00
Cert Store (aka wincert)
This commit is contained in:
91
server/certstore/certstore.go
Normal file
91
server/certstore/certstore.go
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
// Copyright 2022-2023 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.
|
||||||
|
|
||||||
|
package certstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"io"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StoreType int
|
||||||
|
|
||||||
|
const MATCHBYEMPTY = 0
|
||||||
|
const STOREEMPTY = 0
|
||||||
|
|
||||||
|
const (
|
||||||
|
windowsCurrentUser StoreType = iota + 1
|
||||||
|
windowsLocalMachine
|
||||||
|
)
|
||||||
|
|
||||||
|
var StoreMap = map[string]StoreType{
|
||||||
|
"windowscurrentuser": windowsCurrentUser,
|
||||||
|
"windowslocalmachine": windowsLocalMachine,
|
||||||
|
}
|
||||||
|
|
||||||
|
var StoreOSMap = map[StoreType]string{
|
||||||
|
windowsCurrentUser: "windows",
|
||||||
|
windowsLocalMachine: "windows",
|
||||||
|
}
|
||||||
|
|
||||||
|
type MatchByType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
matchByIssuer MatchByType = iota + 1
|
||||||
|
matchBySubject
|
||||||
|
)
|
||||||
|
|
||||||
|
var MatchByMap = map[string]MatchByType{
|
||||||
|
"issuer": matchByIssuer,
|
||||||
|
"subject": matchBySubject,
|
||||||
|
}
|
||||||
|
|
||||||
|
var Usage = `
|
||||||
|
In place of cert_file and key_file you may use the windows certificate store:
|
||||||
|
|
||||||
|
tls {
|
||||||
|
cert_store: "WindowsCurrentUser"
|
||||||
|
cert_match_by: "Subject"
|
||||||
|
cert_match: "MyServer123"
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
func ParseCertStore(certStore string) (StoreType, error) {
|
||||||
|
certStoreType, exists := StoreMap[strings.ToLower(certStore)]
|
||||||
|
if !exists {
|
||||||
|
return 0, ErrBadCertStore
|
||||||
|
}
|
||||||
|
validOS, exists := StoreOSMap[certStoreType]
|
||||||
|
if !exists || validOS != runtime.GOOS {
|
||||||
|
return 0, ErrOSNotCompatCertStore
|
||||||
|
}
|
||||||
|
return certStoreType, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseCertMatchBy(certMatchBy string) (MatchByType, error) {
|
||||||
|
certMatchByType, exists := MatchByMap[strings.ToLower(certMatchBy)]
|
||||||
|
if !exists {
|
||||||
|
return 0, ErrBadMatchByType
|
||||||
|
}
|
||||||
|
return certMatchByType, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// credential provides access to a public key and is a crypto.Signer.
|
||||||
|
type credential interface {
|
||||||
|
// Public returns the public key corresponding to the leaf certificate.
|
||||||
|
Public() crypto.PublicKey
|
||||||
|
// Sign signs digest with the private key.
|
||||||
|
Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error)
|
||||||
|
}
|
||||||
46
server/certstore/certstore_other.go
Normal file
46
server/certstore/certstore_other.go
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
// Copyright 2022-2023 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.
|
||||||
|
|
||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package certstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/tls"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = MATCHBYEMPTY
|
||||||
|
|
||||||
|
// otherKey implements crypto.Signer and crypto.Decrypter to satisfy linter on platforms that don't implement certstore
|
||||||
|
type otherKey struct{}
|
||||||
|
|
||||||
|
func TLSConfig(certStore StoreType, certMatchBy MatchByType, certMatch string, config *tls.Config) error {
|
||||||
|
_, _, _, _ = certStore, certMatchBy, certMatch, config
|
||||||
|
return ErrOSNotCompatCertStore
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public always returns nil public key since this is a stub on non-supported platform
|
||||||
|
func (k otherKey) Public() crypto.PublicKey {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign always returns a nil signature since this is a stub on non-supported platform
|
||||||
|
func (k otherKey) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) {
|
||||||
|
_, _, _ = rand, digest, opts
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify interface conformance.
|
||||||
|
var _ credential = &otherKey{}
|
||||||
827
server/certstore/certstore_windows.go
Normal file
827
server/certstore/certstore_windows.go
Normal file
@@ -0,0 +1,827 @@
|
|||||||
|
// Copyright 2022-2023 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
|
||||||
|
winMyStore = winWide("MY")
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// TLSConfig fulfills the same function as reading cert and key pair from pem files but
|
||||||
|
// sources the Windows certificate store instead
|
||||||
|
func TLSConfig(certStore StoreType, certMatchBy MatchByType, certMatch 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
|
||||||
|
}
|
||||||
|
} 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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{}
|
||||||
73
server/certstore/errors.go
Normal file
73
server/certstore/errors.go
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
package certstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrBadCryptoStoreProvider represents inablity to establish link with a certificate store
|
||||||
|
ErrBadCryptoStoreProvider = errors.New("unable to open certificate store or store not available")
|
||||||
|
|
||||||
|
// ErrBadRSAHashAlgorithm represents a bad or unsupported RSA hash algorithm
|
||||||
|
ErrBadRSAHashAlgorithm = errors.New("unsupported RSA hash algorithm")
|
||||||
|
|
||||||
|
// ErrBadSigningAlgorithm represents a bad or unsupported signing algorithm
|
||||||
|
ErrBadSigningAlgorithm = errors.New("unsupported signing algorithm")
|
||||||
|
|
||||||
|
// ErrStoreRSASigningError represents an error returned from store during RSA signature
|
||||||
|
ErrStoreRSASigningError = errors.New("unable to obtain RSA signature from store")
|
||||||
|
|
||||||
|
// ErrStoreECDSASigningError represents an error returned from store during ECDSA signature
|
||||||
|
ErrStoreECDSASigningError = errors.New("unable to obtain ECDSA signature from store")
|
||||||
|
|
||||||
|
// ErrNoPrivateKeyStoreRef represents an error getting a handle to a private key in store
|
||||||
|
ErrNoPrivateKeyStoreRef = errors.New("unable to obtain private key handle from store")
|
||||||
|
|
||||||
|
// ErrExtractingPrivateKeyMetadata represents a family of errors extracting metadata about the private key in store
|
||||||
|
ErrExtractingPrivateKeyMetadata = errors.New("unable to extract private key metadata")
|
||||||
|
|
||||||
|
// ErrExtractingECCPublicKey represents an error exporting ECC-type public key from store
|
||||||
|
ErrExtractingECCPublicKey = errors.New("unable to extract ECC public key from store")
|
||||||
|
|
||||||
|
// ErrExtractingRSAPublicKey represents an error exporting RSA-type public key from store
|
||||||
|
ErrExtractingRSAPublicKey = errors.New("unable to extract RSA public key from store")
|
||||||
|
|
||||||
|
// ErrExtractingPublicKey represents a general error exporting public key from store
|
||||||
|
ErrExtractingPublicKey = errors.New("unable to extract public key from store")
|
||||||
|
|
||||||
|
// ErrBadPublicKeyAlgorithm represents a bad or unsupported public key algorithm
|
||||||
|
ErrBadPublicKeyAlgorithm = errors.New("unsupported public key algorithm")
|
||||||
|
|
||||||
|
// ErrExtractPropertyFromKey represents a general failure to extract a metadata property field
|
||||||
|
ErrExtractPropertyFromKey = errors.New("unable to extract property from key")
|
||||||
|
|
||||||
|
// ErrBadECCCurveName represents an ECC signature curve name that is bad or unsupported
|
||||||
|
ErrBadECCCurveName = errors.New("unsupported ECC curve name")
|
||||||
|
|
||||||
|
// ErrFailedCertSearch represents not able to find certificate in store
|
||||||
|
ErrFailedCertSearch = errors.New("unable to find certificate in store")
|
||||||
|
|
||||||
|
// ErrFailedX509Extract represents not being able to extract x509 certificate from found cert in store
|
||||||
|
ErrFailedX509Extract = errors.New("unable to extract x509 from certificate")
|
||||||
|
|
||||||
|
// ErrBadMatchByType represents unknown CERT_MATCH_BY passed
|
||||||
|
ErrBadMatchByType = errors.New("cert match by type not implemented")
|
||||||
|
|
||||||
|
// ErrBadCertStore represents unknown CERT_STORE passed
|
||||||
|
ErrBadCertStore = errors.New("cert store type not implemented")
|
||||||
|
|
||||||
|
// ErrConflictCertFileAndStore represents ambiguous configuration of both file and store
|
||||||
|
ErrConflictCertFileAndStore = errors.New("'cert_file' and 'cert_store' may not both be configured")
|
||||||
|
|
||||||
|
// ErrBadCertStoreField represents malformed cert_store option
|
||||||
|
ErrBadCertStoreField = errors.New("expected 'cert_store' to be a valid non-empty string")
|
||||||
|
|
||||||
|
// ErrBadCertMatchByField represents malformed cert_match_by option
|
||||||
|
ErrBadCertMatchByField = errors.New("expected 'cert_match_by' to be a valid non-empty string")
|
||||||
|
|
||||||
|
// ErrBadCertMatchField represents malformed cert_match option
|
||||||
|
ErrBadCertMatchField = errors.New("expected 'cert_match' to be a valid non-empty string")
|
||||||
|
|
||||||
|
// ErrOSNotCompatCertStore represents cert_store passed that exists but is not valid on current OS
|
||||||
|
ErrOSNotCompatCertStore = errors.New("cert_store not compatible with current operating system")
|
||||||
|
)
|
||||||
230
server/certstore_windows_test.go
Normal file
230
server/certstore_windows_test.go
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
// Copyright 2022-2023 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.
|
||||||
|
|
||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/nats-io/nats.go"
|
||||||
|
)
|
||||||
|
|
||||||
|
func runPowershellScript(scriptFile string, args []string) error {
|
||||||
|
_ = args
|
||||||
|
psExec, _ := exec.LookPath("powershell.exe")
|
||||||
|
execArgs := []string{psExec, "-command", fmt.Sprintf("& '%s'", scriptFile)}
|
||||||
|
|
||||||
|
cmdImport := &exec.Cmd{
|
||||||
|
Path: psExec,
|
||||||
|
Args: execArgs,
|
||||||
|
Stdout: os.Stdout,
|
||||||
|
Stderr: os.Stderr,
|
||||||
|
}
|
||||||
|
return cmdImport.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func runConfiguredLeaf(t *testing.T, hubPort int, certStore string, matchBy string, match string, expectedLeafCount int) {
|
||||||
|
|
||||||
|
// Fire up the leaf
|
||||||
|
u, err := url.Parse(fmt.Sprintf("nats://localhost:%d", hubPort))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error parsing url: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
configStr := fmt.Sprintf(`
|
||||||
|
port: -1
|
||||||
|
leaf {
|
||||||
|
remotes [
|
||||||
|
{
|
||||||
|
url: "%s"
|
||||||
|
tls {
|
||||||
|
cert_store: "%s"
|
||||||
|
cert_match_by: "%s"
|
||||||
|
cert_match: "%s"
|
||||||
|
|
||||||
|
# Above should be equivalent to:
|
||||||
|
# cert_file: "../test/configs/certs/tlsauth/client.pem"
|
||||||
|
# key_file: "../test/configs/certs/tlsauth/client-key.pem"
|
||||||
|
|
||||||
|
ca_file: "../test/configs/certs/tlsauth/ca.pem"
|
||||||
|
timeout: 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
`, u.String(), certStore, matchBy, match)
|
||||||
|
|
||||||
|
leafConfig := createConfFile(t, []byte(configStr))
|
||||||
|
defer removeFile(t, leafConfig)
|
||||||
|
leafServer, _ := RunServerWithConfig(leafConfig)
|
||||||
|
defer leafServer.Shutdown()
|
||||||
|
|
||||||
|
// After client verify, hub will match by SAN email, SAN dns, and Subject (in that order)
|
||||||
|
// Our test client specifies Subject only so we should match on that...
|
||||||
|
|
||||||
|
// A little settle time
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
checkLeafNodeConnectedCount(t, leafServer, expectedLeafCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestLeafTLSWindowsCertStore tests the topology of two NATS Servers connected as leaf and hub with authentication of
|
||||||
|
// leaf to hub via mTLS with leaf's certificate and signing key provisioned in the Windows certificate store.
|
||||||
|
func TestLeafTLSWindowsCertStore(t *testing.T) {
|
||||||
|
|
||||||
|
// Client Identity (client.pem)
|
||||||
|
// Issuer: O = Synadia Communications Inc., OU = NATS.io, CN = localhost
|
||||||
|
// Subject: OU = NATS.io, CN = example.com
|
||||||
|
|
||||||
|
// Make sure windows cert store is reset to avoid conflict with other tests
|
||||||
|
err := runPowershellScript("../test/configs/certs/tlsauth/certstore/delete-cert-from-store.ps1", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected powershell cert delete to succeed: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provision Windows cert store with client cert and secret
|
||||||
|
err = runPowershellScript("../test/configs/certs/tlsauth/certstore/import-p12-client.ps1", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected powershell provision to succeed: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fire up the hub
|
||||||
|
hubConfig := createConfFile(t, []byte(`
|
||||||
|
port: -1
|
||||||
|
leaf {
|
||||||
|
listen: "127.0.0.1:-1"
|
||||||
|
tls {
|
||||||
|
ca_file: "../test/configs/certs/tlsauth/ca.pem"
|
||||||
|
cert_file: "../test/configs/certs/tlsauth/server.pem"
|
||||||
|
key_file: "../test/configs/certs/tlsauth/server-key.pem"
|
||||||
|
timeout: 5
|
||||||
|
verify_and_map: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
accounts: {
|
||||||
|
AcctA: {
|
||||||
|
users: [ {user: "OU = NATS.io, CN = example.com"} ]
|
||||||
|
},
|
||||||
|
AcctB: {
|
||||||
|
users: [ {user: UserB1} ]
|
||||||
|
},
|
||||||
|
SYS: {
|
||||||
|
users: [ {user: System} ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
system_account: "SYS"
|
||||||
|
`))
|
||||||
|
defer removeFile(t, hubConfig)
|
||||||
|
hubServer, hubOptions := RunServerWithConfig(hubConfig)
|
||||||
|
defer hubServer.Shutdown()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
certStore string
|
||||||
|
certMatchBy string
|
||||||
|
certMatch string
|
||||||
|
expectedLeafCount int
|
||||||
|
}{
|
||||||
|
{"WindowsCurrentUser", "Subject", "example.com", 1},
|
||||||
|
{"WindowsCurrentUser", "Issuer", "Synadia Communications Inc.", 1},
|
||||||
|
{"WindowsCurrentUser", "Issuer", "Frodo Baggins, Inc.", 0},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(fmt.Sprintf("%s by %s match %s", tc.certStore, tc.certMatchBy, tc.certMatch), func(t *testing.T) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
if tc.expectedLeafCount != 0 {
|
||||||
|
t.Fatalf("did not expect panic")
|
||||||
|
} else {
|
||||||
|
if !strings.Contains(fmt.Sprintf("%v", r), "Error processing configuration file") {
|
||||||
|
t.Fatalf("did not expect unknown panic cause")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
runConfiguredLeaf(t, hubOptions.LeafNode.Port, tc.certStore, tc.certMatchBy, tc.certMatch, tc.expectedLeafCount)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestServerTLSWindowsCertStore tests the topology of a NATS server requiring TLS and gettings it own server
|
||||||
|
// cert identiy (as used when accepting NATS client connections and negotiating TLS) from Windows certificate store.
|
||||||
|
func TestServerTLSWindowsCertStore(t *testing.T) {
|
||||||
|
|
||||||
|
// Server Identity (server.pem)
|
||||||
|
// Issuer: O = Synadia Communications Inc., OU = NATS.io, CN = localhost
|
||||||
|
// Subject: OU = NATS.io Operators, CN = localhost
|
||||||
|
|
||||||
|
// Make sure windows cert store is reset to avoid conflict with other tests
|
||||||
|
err := runPowershellScript("../test/configs/certs/tlsauth/certstore/delete-cert-from-store.ps1", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected powershell cert delete to succeed: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provision Windows cert store with server cert and secret
|
||||||
|
err = runPowershellScript("../test/configs/certs/tlsauth/certstore/import-p12-server.ps1", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected powershell provision to succeed: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fire up the server
|
||||||
|
srvConfig := createConfFile(t, []byte(`
|
||||||
|
listen: "localhost:-1"
|
||||||
|
tls {
|
||||||
|
cert_store: "WindowsCurrentUser"
|
||||||
|
cert_match_by: "Subject"
|
||||||
|
cert_match: "NATS.io Operators"
|
||||||
|
timeout: 5
|
||||||
|
}
|
||||||
|
`))
|
||||||
|
defer removeFile(t, srvConfig)
|
||||||
|
srvServer, _ := RunServerWithConfig(srvConfig)
|
||||||
|
if srvServer == nil {
|
||||||
|
t.Fatalf("expected to be able start server with cert store configuration")
|
||||||
|
}
|
||||||
|
defer srvServer.Shutdown()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
clientCA string
|
||||||
|
expect bool
|
||||||
|
}{
|
||||||
|
{"../test/configs/certs/tlsauth/ca.pem", true},
|
||||||
|
{"../test/configs/certs/tlsauth/client.pem", false},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(fmt.Sprintf("Client CA: %s", tc.clientCA), func(t *testing.T) {
|
||||||
|
nc, _ := nats.Connect(srvServer.clientConnectURLs[0], nats.RootCAs(tc.clientCA))
|
||||||
|
err := nc.Publish("foo", []byte("hello TLS server-authenticated server"))
|
||||||
|
if (err != nil) == tc.expect {
|
||||||
|
t.Fatalf("expected publish result %v to TLS authenticated server", tc.expect)
|
||||||
|
}
|
||||||
|
nc.Close()
|
||||||
|
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
nc, _ = nats.Connect(srvServer.clientConnectURLs[0], nats.RootCAs(tc.clientCA))
|
||||||
|
err = nc.Publish("foo", []byte("hello TLS server-authenticated server"))
|
||||||
|
if (err != nil) == tc.expect {
|
||||||
|
t.Fatalf("expected repeated connection result %v to TLS authenticated server", tc.expect)
|
||||||
|
}
|
||||||
|
nc.Close()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,9 +34,9 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/nats-io/jwt/v2"
|
"github.com/nats-io/jwt/v2"
|
||||||
"github.com/nats-io/nkeys"
|
|
||||||
|
|
||||||
"github.com/nats-io/nats-server/v2/conf"
|
"github.com/nats-io/nats-server/v2/conf"
|
||||||
|
"github.com/nats-io/nats-server/v2/server/certstore"
|
||||||
|
"github.com/nats-io/nkeys"
|
||||||
)
|
)
|
||||||
|
|
||||||
var allowUnknownTopLevelField = int32(0)
|
var allowUnknownTopLevelField = int32(0)
|
||||||
@@ -53,7 +53,7 @@ func NoErrOnUnknownFields(noError bool) {
|
|||||||
atomic.StoreInt32(&allowUnknownTopLevelField, val)
|
atomic.StoreInt32(&allowUnknownTopLevelField, val)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set of lower case hex-encoded sha256 of DER encoded SubjectPublicKeyInfo
|
// PinnedCertSet is a set of lower case hex-encoded sha256 of DER encoded SubjectPublicKeyInfo
|
||||||
type PinnedCertSet map[string]struct{}
|
type PinnedCertSet map[string]struct{}
|
||||||
|
|
||||||
// ClusterOpts are options for clusters.
|
// ClusterOpts are options for clusters.
|
||||||
@@ -574,6 +574,9 @@ type TLSConfigOpts struct {
|
|||||||
Ciphers []uint16
|
Ciphers []uint16
|
||||||
CurvePreferences []tls.CurveID
|
CurvePreferences []tls.CurveID
|
||||||
PinnedCerts PinnedCertSet
|
PinnedCerts PinnedCertSet
|
||||||
|
CertStore certstore.StoreType
|
||||||
|
CertMatchBy certstore.MatchByType
|
||||||
|
CertMatch string
|
||||||
}
|
}
|
||||||
|
|
||||||
// OCSPConfig represents the options of OCSP stapling options.
|
// OCSPConfig represents the options of OCSP stapling options.
|
||||||
@@ -3847,6 +3850,9 @@ func PrintTLSHelpAndDie() {
|
|||||||
for k := range curvePreferenceMap {
|
for k := range curvePreferenceMap {
|
||||||
fmt.Printf(" %s\n", k)
|
fmt.Printf(" %s\n", k)
|
||||||
}
|
}
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
fmt.Printf("%s", certstore.Usage)
|
||||||
|
}
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4004,6 +4010,32 @@ func parseTLS(v interface{}, isClientCtx bool) (t *TLSConfigOpts, retErr error)
|
|||||||
}
|
}
|
||||||
tc.PinnedCerts = wl
|
tc.PinnedCerts = wl
|
||||||
}
|
}
|
||||||
|
case "cert_store":
|
||||||
|
certStore, ok := mv.(string)
|
||||||
|
if !ok || certStore == _EMPTY_ {
|
||||||
|
return nil, &configErr{tk, certstore.ErrBadCertStoreField.Error()}
|
||||||
|
}
|
||||||
|
certStoreType, err := certstore.ParseCertStore(certStore)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &configErr{tk, err.Error()}
|
||||||
|
}
|
||||||
|
tc.CertStore = certStoreType
|
||||||
|
case "cert_match_by":
|
||||||
|
certMatchBy, ok := mv.(string)
|
||||||
|
if !ok || certMatchBy == _EMPTY_ {
|
||||||
|
return nil, &configErr{tk, certstore.ErrBadCertMatchByField.Error()}
|
||||||
|
}
|
||||||
|
certMatchByType, err := certstore.ParseCertMatchBy(certMatchBy)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &configErr{tk, err.Error()}
|
||||||
|
}
|
||||||
|
tc.CertMatchBy = certMatchByType
|
||||||
|
case "cert_match":
|
||||||
|
certMatch, ok := mv.(string)
|
||||||
|
if !ok || certMatch == _EMPTY_ {
|
||||||
|
return nil, &configErr{tk, certstore.ErrBadCertMatchField.Error()}
|
||||||
|
}
|
||||||
|
tc.CertMatch = certMatch
|
||||||
default:
|
default:
|
||||||
return nil, &configErr{tk, fmt.Sprintf("error parsing tls config, unknown field [%q]", mk)}
|
return nil, &configErr{tk, fmt.Sprintf("error parsing tls config, unknown field [%q]", mk)}
|
||||||
}
|
}
|
||||||
@@ -4290,11 +4322,13 @@ func GenTLSConfig(tc *TLSConfigOpts) (*tls.Config, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case tc.CertFile != "" && tc.KeyFile == "":
|
case tc.CertFile != _EMPTY_ && tc.CertStore != certstore.STOREEMPTY:
|
||||||
|
return nil, certstore.ErrConflictCertFileAndStore
|
||||||
|
case tc.CertFile != _EMPTY_ && tc.KeyFile == _EMPTY_:
|
||||||
return nil, fmt.Errorf("missing 'key_file' in TLS configuration")
|
return nil, fmt.Errorf("missing 'key_file' in TLS configuration")
|
||||||
case tc.CertFile == "" && tc.KeyFile != "":
|
case tc.CertFile == _EMPTY_ && tc.KeyFile != _EMPTY_:
|
||||||
return nil, fmt.Errorf("missing 'cert_file' in TLS configuration")
|
return nil, fmt.Errorf("missing 'cert_file' in TLS configuration")
|
||||||
case tc.CertFile != "" && tc.KeyFile != "":
|
case tc.CertFile != _EMPTY_ && tc.KeyFile != _EMPTY_:
|
||||||
// Now load in cert and private key
|
// Now load in cert and private key
|
||||||
cert, err := tls.LoadX509KeyPair(tc.CertFile, tc.KeyFile)
|
cert, err := tls.LoadX509KeyPair(tc.CertFile, tc.KeyFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -4305,6 +4339,11 @@ func GenTLSConfig(tc *TLSConfigOpts) (*tls.Config, error) {
|
|||||||
return nil, fmt.Errorf("error parsing certificate: %v", err)
|
return nil, fmt.Errorf("error parsing certificate: %v", err)
|
||||||
}
|
}
|
||||||
config.Certificates = []tls.Certificate{cert}
|
config.Certificates = []tls.Certificate{cert}
|
||||||
|
case tc.CertStore != certstore.STOREEMPTY:
|
||||||
|
err := certstore.TLSConfig(tc.CertStore, tc.CertMatchBy, tc.CertMatch, &config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Require client certificates as needed
|
// Require client certificates as needed
|
||||||
|
|||||||
BIN
test/configs/certs/tlsauth/certstore/client.p12
Normal file
BIN
test/configs/certs/tlsauth/certstore/client.p12
Normal file
Binary file not shown.
@@ -0,0 +1,2 @@
|
|||||||
|
$issuer="Synadia Communications Inc."
|
||||||
|
Get-ChildItem Cert:\CurrentUser\My | Where-Object {$_.Issuer -match $issuer} | Remove-Item
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
$fileLocale = $PSScriptRoot + "\client.p12"
|
||||||
|
$Pass = ConvertTo-SecureString -String 's3cr3t' -Force -AsPlainText
|
||||||
|
$User = "whatever"
|
||||||
|
$Cred = New-Object -TypeName "System.Management.Automation.PSCredential" -ArgumentList $User, $Pass
|
||||||
|
Import-PfxCertificate -FilePath $filelocale -CertStoreLocation Cert:\CurrentUser\My -Password $Cred.Password
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
$fileLocale = $PSScriptRoot + "\server.p12"
|
||||||
|
$Pass = ConvertTo-SecureString -String 's3cr3t' -Force -AsPlainText
|
||||||
|
$User = "whatever"
|
||||||
|
$Cred = New-Object -TypeName "System.Management.Automation.PSCredential" -ArgumentList $User, $Pass
|
||||||
|
Import-PfxCertificate -FilePath $filelocale -CertStoreLocation Cert:\CurrentUser\My -Password $Cred.Password
|
||||||
22
test/configs/certs/tlsauth/certstore/pkcs12.md
Normal file
22
test/configs/certs/tlsauth/certstore/pkcs12.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# PKCS12 Files
|
||||||
|
|
||||||
|
Refresh PKCS12 files when test certificates and keys (PEM files) are refreshed (e.g. expiry workflow)
|
||||||
|
|
||||||
|
- `client.p12` is a p12/pfx packaging of `client.pem` and `client-key.pem`
|
||||||
|
|
||||||
|
`openssl pkcs12 -export -inkey ./client-key.pem -in ./client.pem -out client.p12`
|
||||||
|
|
||||||
|
> Note: set the PKCS12 bundle password to `s3cr3t` as required by provisioning scripts
|
||||||
|
|
||||||
|
## Cert Store Provisioning Scripts
|
||||||
|
|
||||||
|
Windows cert store supports p12/pfx bundle for certificate-with-key import. Windows cert store tests will execute
|
||||||
|
a Powershell script to import relevant PKCS12 bundle into the Windows store before the test. Equivalent to:
|
||||||
|
|
||||||
|
`powershell.exe -command "& '..\test\configs\certs\tlsauth\certstore\import-<client,server>-p12.ps1'"`
|
||||||
|
|
||||||
|
The `delete-cert-from-store.ps1` script deletes imported certificates from the Windows store (if present) that can
|
||||||
|
cause side-effects and impact the validity of different use tests.
|
||||||
|
|
||||||
|
> Note: Tests are configured for "current user" store context. Execute tests with appropriate Windows permissions
|
||||||
|
> (e.g. as Admin) if adding tests with "local machine" store context specified.
|
||||||
BIN
test/configs/certs/tlsauth/certstore/server.p12
Normal file
BIN
test/configs/certs/tlsauth/certstore/server.p12
Normal file
Binary file not shown.
Reference in New Issue
Block a user