From 6c776c853d59a57189aacb740f85e0a7a7968d23 Mon Sep 17 00:00:00 2001 From: Josh Drake Date: Mon, 13 Oct 2025 15:43:33 -0500 Subject: [PATCH 1/6] Allow certificate retrieval by friendly name or description. --- kms/capi/capi.go | 24 +++++++++++- kms/capi/ncrypt_windows.go | 76 ++++++++++++++++++++++++++++++++++++++ kms/tpmkms/uri.go | 6 ++- 3 files changed, 104 insertions(+), 2 deletions(-) diff --git a/kms/capi/capi.go b/kms/capi/capi.go index f8cc92d3..06e6f9fa 100644 --- a/kms/capi/capi.go +++ b/kms/capi/capi.go @@ -38,6 +38,8 @@ const ( HashArg = "sha1" StoreLocationArg = "store-location" // 'machine', 'user', etc StoreNameArg = "store" // 'MY', 'CA', 'ROOT', etc + FriendlyNameArg = "friendly-name" + DescriptionArg = "description" KeyIDArg = "key-id" SubjectCNArg = "cn" SerialNumberArg = "serial" @@ -315,6 +317,8 @@ func (k *CAPIKMS) getCertContext(req *apiv1.LoadCertificateRequest) (*windows.Ce issuerName := u.Get(IssuerNameArg) subjectCN := u.Get(SubjectCNArg) serialNumber := u.Get(SerialNumberArg) + friendlyName := u.Get(FriendlyNameArg) + description := u.Get(DescriptionArg) // default to the user store var storeLocation string @@ -390,7 +394,7 @@ func (k *CAPIKMS) getCertContext(req *apiv1.LoadCertificateRequest) (*windows.Ce if handle == nil { return nil, apiv1.NotFoundError{Message: fmt.Sprintf("certificate with %s=%s not found", KeyIDArg, keyID)} } - case issuerName != "" && (serialNumber != "" || subjectCN != ""): + case issuerName != "" && (serialNumber != "" || subjectCN != "" || friendlyName != "" || description != ""): var prevCert *windows.CertContext for { handle, err = findCertificateInStore(st, @@ -439,6 +443,24 @@ func (k *CAPIKMS) getCertContext(req *apiv1.LoadCertificateRequest) (*windows.Ce if x509Cert.Subject.CommonName == subjectCN { return handle, nil } + case len(friendlyName) > 0: + val, err := cryptFindCertificateFriendlyName(prevCert) + if err != nil { + return nil, fmt.Errorf("cryptFindCertificateFriendlyName failed: %w", err) + } + + if val == friendlyName { + return handle, nil + } + case len(description) > 0: + val, err := cryptFindCertificateDescription(prevCert) + if err != nil { + return nil, fmt.Errorf("cryptFindCertificateDescription failed: %w", err) + } + + if val == description { + return handle, nil + } } prevCert = handle diff --git a/kms/capi/ncrypt_windows.go b/kms/capi/ncrypt_windows.go index 378a8b56..e7b20a7a 100644 --- a/kms/capi/ncrypt_windows.go +++ b/kms/capi/ncrypt_windows.go @@ -61,9 +61,11 @@ const ( compareShift = 16 // CERT_COMPARE_SHIFT compareSHA1Hash = 1 // CERT_COMPARE_SHA1_HASH compareCertID = 16 // CERT_COMPARE_CERT_ID + compareProp = 5 // CERT_COMPARE_CERT_ID findIssuerStr = compareNameStrW< Date: Mon, 13 Oct 2025 16:19:49 -0500 Subject: [PATCH 2/6] Improve friendly name and description CAPI getters. --- kms/capi/ncrypt_windows.go | 75 +++++++++++++++----------------------- 1 file changed, 29 insertions(+), 46 deletions(-) diff --git a/kms/capi/ncrypt_windows.go b/kms/capi/ncrypt_windows.go index e7b20a7a..ea7399e3 100644 --- a/kms/capi/ncrypt_windows.go +++ b/kms/capi/ncrypt_windows.go @@ -611,36 +611,34 @@ func cryptFindCertificateKeyContainerName(certContext *windows.CertContext) (str return "", nil } -func cryptFindCertificateFriendlyName(certContext *windows.CertContext) (string, error) { - var size uint32 - - r1, _, err := procCertGetCertificateContextProperty.Call( +func certGetCertificateContextProperty(certContext *windows.CertContext, propID uint32, pvData *byte, pcbData *uint32) error { + r0, _, err := procCertGetCertificateContextProperty.Call( uintptr(unsafe.Pointer(certContext)), - uintptr(CERT_FRIENDLY_NAME_PROP_ID), - uintptr(0), - uintptr(unsafe.Pointer(&size)), + uintptr(propID), + uintptr(unsafe.Pointer(pvData)), + uintptr(unsafe.Pointer(pcbData)), ) - if !errors.Is(err, windows.Errno(0)) { - return "", fmt.Errorf("CertGetCertificateContextProperty returned %w", err) - } - if r1 == 0 { - return "", fmt.Errorf("finding certificate friendly name failed: %v", errNoToStr(uint32(r1))) + if r0 == 0 { + return err } + return nil +} - buf := make([]byte, size) - r2, _, err := procCertGetCertificateContextProperty.Call( - uintptr(unsafe.Pointer(certContext)), - uintptr(CERT_KEY_PROV_INFO_PROP_ID), - uintptr(0), - uintptr(unsafe.Pointer(&buf[0])), - ) +func cryptFindCertificateFriendlyName(certContext *windows.CertContext) (string, error) { + var size uint32 - if !errors.Is(err, windows.Errno(0)) { - return "", fmt.Errorf("CertGetCertificateContextProperty returned %w", err) + err := certGetCertificateContextProperty(certContext, CERT_FRIENDLY_NAME_PROP_ID, nil, &size) + if err != nil { + return "", err + } + if size == 0 { + return "", fmt.Errorf("certificate has no friendly name") } - if r2 == 0 { - return "", fmt.Errorf("finding certificate friendly name failed: %v", errNoToStr(uint32(r2))) + buf := make([]byte, size) + err = certGetCertificateContextProperty(certContext, CERT_FRIENDLY_NAME_PROP_ID, &buf[0], &size) + if err != nil { + return "", err } uc := bytes.ReplaceAll(buf, []byte{0x00}, []byte("")) @@ -650,33 +648,18 @@ func cryptFindCertificateFriendlyName(certContext *windows.CertContext) (string, func cryptFindCertificateDescription(certContext *windows.CertContext) (string, error) { var size uint32 - r1, _, err := procCertGetCertificateContextProperty.Call( - uintptr(unsafe.Pointer(certContext)), - uintptr(CERT_DESCRIPTION_PROP_ID), - uintptr(0), - uintptr(unsafe.Pointer(&size)), - ) - if !errors.Is(err, windows.Errno(0)) { - return "", fmt.Errorf("CertGetCertificateContextProperty returned %w", err) + err := certGetCertificateContextProperty(certContext, CERT_DESCRIPTION_PROP_ID, nil, &size) + if err != nil { + return "", err } - if r1 == 0 { - return "", fmt.Errorf("finding certificate description failed: %v", errNoToStr(uint32(r1))) + if size == 0 { + return "", fmt.Errorf("certificate has no description") } buf := make([]byte, size) - r2, _, err := procCertGetCertificateContextProperty.Call( - uintptr(unsafe.Pointer(certContext)), - uintptr(CERT_KEY_PROV_INFO_PROP_ID), - uintptr(0), - uintptr(unsafe.Pointer(&buf[0])), - ) - - if !errors.Is(err, windows.Errno(0)) { - return "", fmt.Errorf("CertGetCertificateContextProperty returned %w", err) - } - - if r2 == 0 { - return "", fmt.Errorf("finding certificate description failed: %v", errNoToStr(uint32(r2))) + err = certGetCertificateContextProperty(certContext, CERT_DESCRIPTION_PROP_ID, &buf[0], &size) + if err != nil { + return "", err } uc := bytes.ReplaceAll(buf, []byte{0x00}, []byte("")) From 94a9c5f79b33ffe167677e67dbde406310601d65 Mon Sep 17 00:00:00 2001 From: Josh Drake Date: Mon, 13 Oct 2025 16:34:06 -0500 Subject: [PATCH 3/6] Correctly handle NOT_FOUND errors for CAPI certificate props. --- kms/capi/capi.go | 4 ++-- kms/capi/ncrypt_windows.go | 15 ++++++++++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/kms/capi/capi.go b/kms/capi/capi.go index 06e6f9fa..ec463961 100644 --- a/kms/capi/capi.go +++ b/kms/capi/capi.go @@ -444,7 +444,7 @@ func (k *CAPIKMS) getCertContext(req *apiv1.LoadCertificateRequest) (*windows.Ce return handle, nil } case len(friendlyName) > 0: - val, err := cryptFindCertificateFriendlyName(prevCert) + val, err := cryptFindCertificateFriendlyName(handle) if err != nil { return nil, fmt.Errorf("cryptFindCertificateFriendlyName failed: %w", err) } @@ -453,7 +453,7 @@ func (k *CAPIKMS) getCertContext(req *apiv1.LoadCertificateRequest) (*windows.Ce return handle, nil } case len(description) > 0: - val, err := cryptFindCertificateDescription(prevCert) + val, err := cryptFindCertificateDescription(handle) if err != nil { return nil, fmt.Errorf("cryptFindCertificateDescription failed: %w", err) } diff --git a/kms/capi/ncrypt_windows.go b/kms/capi/ncrypt_windows.go index ea7399e3..ab0f24bf 100644 --- a/kms/capi/ncrypt_windows.go +++ b/kms/capi/ncrypt_windows.go @@ -61,7 +61,7 @@ const ( compareShift = 16 // CERT_COMPARE_SHIFT compareSHA1Hash = 1 // CERT_COMPARE_SHA1_HASH compareCertID = 16 // CERT_COMPARE_CERT_ID - compareProp = 5 // CERT_COMPARE_CERT_ID + compareProp = 5 // CERT_COMPARE_CERT_ID findIssuerStr = compareNameStrW< Date: Mon, 13 Oct 2025 17:37:55 -0500 Subject: [PATCH 4/6] Add friendly name and description CAPI setters. --- kms/capi/capi.go | 8 ++++++++ kms/capi/ncrypt_windows.go | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/kms/capi/capi.go b/kms/capi/capi.go index ec463961..118d0071 100644 --- a/kms/capi/capi.go +++ b/kms/capi/capi.go @@ -770,6 +770,14 @@ func (k *CAPIKMS) StoreCertificate(req *apiv1.StoreCertificateRequest) error { cryptFindCertificateKeyProvInfo(certContext) } + if friendlyName := u.Get(FriendlyNameArg); friendlyName != "" { + cryptSetCertificateFriendlyName(certContext, friendlyName) + } + + if description := u.Get(DescriptionArg); description != "" { + cryptSetCertificateDescription(certContext, description) + } + st, err := windows.CertOpenStore( certStoreProvSystem, 0, diff --git a/kms/capi/ncrypt_windows.go b/kms/capi/ncrypt_windows.go index ab0f24bf..42a608e6 100644 --- a/kms/capi/ncrypt_windows.go +++ b/kms/capi/ncrypt_windows.go @@ -156,6 +156,7 @@ var ( procCertFindCertificateInStore = crypt32.MustFindProc("CertFindCertificateInStore") procCryptFindCertificateKeyProvInfo = crypt32.MustFindProc("CryptFindCertificateKeyProvInfo") procCertGetCertificateContextProperty = crypt32.MustFindProc("CertGetCertificateContextProperty") + procCertSetCertificateContextProperty = crypt32.MustFindProc("CertSetCertificateContextProperty") procCertStrToName = crypt32.MustFindProc("CertStrToNameW") ) @@ -611,6 +612,38 @@ func cryptFindCertificateKeyContainerName(certContext *windows.CertContext) (str return "", nil } +func certSetCertificateContextProperty(certContext *windows.CertContext, propID uint32, pvData uintptr) error { + r0, _, err := procCertSetCertificateContextProperty.Call( + uintptr(unsafe.Pointer(certContext)), + uintptr(propID), + 0, + pvData, + ) + + if r0 == 0 { + return err + } + return nil +} + +func cryptSetCertificateFriendlyName(certContext *windows.CertContext, val string) error { + data := CRYPTOAPI_BLOB{ + len: uint32(len(val)+1) * 2, + data: uintptr(unsafe.Pointer(wide(val))), + } + + return certSetCertificateContextProperty(certContext, CERT_FRIENDLY_NAME_PROP_ID, uintptr(unsafe.Pointer(&data))) +} + +func cryptSetCertificateDescription(certContext *windows.CertContext, val string) error { + data := CRYPTOAPI_BLOB{ + len: uint32(len(val)+1) * 2, + data: uintptr(unsafe.Pointer(wide(val))), + } + + return certSetCertificateContextProperty(certContext, CERT_DESCRIPTION_PROP_ID, uintptr(unsafe.Pointer(&data))) +} + func certGetCertificateContextProperty(certContext *windows.CertContext, propID uint32, pvData *byte, pcbData *uint32) error { r0, _, err := procCertGetCertificateContextProperty.Call( uintptr(unsafe.Pointer(certContext)), From 5a5464639e30751aa41a85025a680f1ab7ddee26 Mon Sep 17 00:00:00 2001 From: Josh Drake Date: Mon, 13 Oct 2025 17:39:01 -0500 Subject: [PATCH 5/6] Pass through 'friendly-name' and 'description' URI params to CAPI KMS. --- kms/tpmkms/tpmkms.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kms/tpmkms/tpmkms.go b/kms/tpmkms/tpmkms.go index a46e2a1e..a1707a54 100644 --- a/kms/tpmkms/tpmkms.go +++ b/kms/tpmkms/tpmkms.go @@ -875,6 +875,8 @@ func (k *TPMKMS) storeCertificateChainToWindowsCertificateStore(req *apiv1.Store uv.Set("sha1", fp) uv.Set("store-location", location) uv.Set("store", store) + uv.Set("friendly-name", o.friendlyName) + uv.Set("description", o.description) uv.Set("skip-find-certificate-key", skipFindCertificateKey) if err := k.windowsCertificateManager.StoreCertificate(&apiv1.StoreCertificateRequest{ From 474f3b2e61c561ec9a21bda72ca434e505da60a0 Mon Sep 17 00:00:00 2001 From: Josh Drake Date: Mon, 13 Oct 2025 17:51:06 -0500 Subject: [PATCH 6/6] Handle additional parameters in Windows certificate chain loading. --- kms/tpmkms/tpmkms.go | 30 ++++++++++++++++++++++-------- kms/tpmkms/uri.go | 4 +++- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/kms/tpmkms/tpmkms.go b/kms/tpmkms/tpmkms.go index a1707a54..df2dacb8 100644 --- a/kms/tpmkms/tpmkms.go +++ b/kms/tpmkms/tpmkms.go @@ -707,11 +707,14 @@ const ( ) func (k *TPMKMS) loadCertificateChainFromWindowsCertificateStore(req *apiv1.LoadCertificateRequest) ([]*x509.Certificate, error) { - pub, err := k.GetPublicKey(&apiv1.GetPublicKeyRequest{ + var subjectKeyID string + if pub, err := k.GetPublicKey(&apiv1.GetPublicKeyRequest{ Name: req.Name, - }) - if err != nil { - return nil, fmt.Errorf("failed retrieving public key: %w", err) + }); err == nil { + subjectKeyID, err = generateWindowsSubjectKeyID(pub) + if err != nil { + return nil, fmt.Errorf("failed generating subject key id: %w", err) + } } o, err := parseNameURI(req.Name) @@ -728,13 +731,24 @@ func (k *TPMKMS) loadCertificateChainFromWindowsCertificateStore(req *apiv1.Load store = o.store } - subjectKeyID, err := generateWindowsSubjectKeyID(pub) - if err != nil { - return nil, fmt.Errorf("failed generating subject key id: %w", err) + uv := url.Values{ + "store-location": []string{location}, + "store": []string{store}, + } + + switch { + case subjectKeyID != "": + uv.Set("key-id", subjectKeyID) + case o.issuer != "": + uv.Set("issuer", o.issuer) + case o.friendlyName != "": + uv.Set("friendly-name", o.friendlyName) + case o.description != "": + uv.Set("description", o.description) } cert, err := k.windowsCertificateManager.LoadCertificate(&apiv1.LoadCertificateRequest{ - Name: fmt.Sprintf("capi:key-id=%s;store-location=%s;store=%s;", subjectKeyID, location, store), + Name: uri.New("capi", uv).String(), }) if err != nil { return nil, fmt.Errorf("failed retrieving certificate using Windows platform cryptography provider: %w", err) diff --git a/kms/tpmkms/uri.go b/kms/tpmkms/uri.go index 35652804..6035d041 100644 --- a/kms/tpmkms/uri.go +++ b/kms/tpmkms/uri.go @@ -60,8 +60,10 @@ func parseNameURI(nameURI string) (o objectProperties, err error) { o.qualifyingData = qualifyingData } - // store location, store options are used on Windows to override + // store location and store options are used on Windows to override // which store(s) are used for storing and loading (intermediate) certificates + // friendly-name and description are used on Windows to populate additional certificate + // context properties to aid in retrieval o.storeLocation = u.Get("store-location") o.store = u.Get("store") o.friendlyName = u.Get("friendly-name")