Skip to content

Commit 0d5c65f

Browse files
add ECC test case and review items
1 parent fde4606 commit 0d5c65f

5 files changed

Lines changed: 132 additions & 20 deletions

File tree

.github/workflows/windows-cert-store-test.yml

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -169,16 +169,24 @@ jobs:
169169
include:
170170
- server_key_source: file
171171
client_key_source: x509
172+
key_algorithm: rsa
172173
test_name: "Server-File-Client-X509"
173174
- server_key_source: store
174175
client_key_source: x509
176+
key_algorithm: rsa
175177
test_name: "Server-Store-Client-X509"
176178
- server_key_source: file
177179
client_key_source: store
180+
key_algorithm: rsa
178181
test_name: "Server-File-Client-Store"
179182
- server_key_source: store
180183
client_key_source: store
184+
key_algorithm: rsa
181185
test_name: "Server-Store-Client-Store"
186+
- server_key_source: store
187+
client_key_source: x509
188+
key_algorithm: ecdsa
189+
test_name: "Server-Store-Client-X509-ECDSA"
182190

183191
steps:
184192
- uses: actions/checkout@v4
@@ -269,14 +277,24 @@ jobs:
269277
# For server: use LocalMachine so service (LocalSystem) can access it
270278
# For client: use CurrentUser (accessed by testuser)
271279
if ("${{ matrix.server_key_source }}" -eq "store") {
272-
$serverCert = New-SelfSignedCertificate `
273-
-Subject "CN=wolfSSH-Test-Server" `
274-
-KeyAlgorithm RSA `
275-
-KeyLength 2048 `
276-
-CertStoreLocation "Cert:\LocalMachine\My" `
277-
-KeyExportPolicy Exportable `
278-
-NotAfter (Get-Date).AddYears(1) `
279-
-KeyUsage DigitalSignature, KeyEncipherment
280+
if ("${{ matrix.key_algorithm }}" -eq "ecdsa") {
281+
$serverCert = New-SelfSignedCertificate `
282+
-Subject "CN=wolfSSH-Test-Server" `
283+
-KeyAlgorithm ECDSA_nistP256 `
284+
-CertStoreLocation "Cert:\LocalMachine\My" `
285+
-KeyExportPolicy Exportable `
286+
-NotAfter (Get-Date).AddYears(1) `
287+
-KeyUsage DigitalSignature
288+
} else {
289+
$serverCert = New-SelfSignedCertificate `
290+
-Subject "CN=wolfSSH-Test-Server" `
291+
-KeyAlgorithm RSA `
292+
-KeyLength 2048 `
293+
-CertStoreLocation "Cert:\LocalMachine\My" `
294+
-KeyExportPolicy Exportable `
295+
-NotAfter (Get-Date).AddYears(1) `
296+
-KeyUsage DigitalSignature, KeyEncipherment
297+
}
280298
Write-Host "Server cert created in LocalMachine: $($serverCert.Subject)"
281299
282300
# Grant LocalSystem (NT AUTHORITY\SYSTEM) access to the private key.
@@ -289,7 +307,7 @@ jobs:
289307
# Try multiple methods to get the private key info
290308
$keyFound = $false
291309
292-
# Method 1: Try RSACertificateExtensions
310+
# Method 1: Try RSACertificateExtensions (RSA keys)
293311
try {
294312
Write-Host "Trying RSACertificateExtensions.GetRSAPrivateKey..."
295313
$rsaKey = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($serverCert)
@@ -311,6 +329,23 @@ jobs:
311329
Write-Host " RSACertificateExtensions method failed: $_"
312330
}
313331
332+
# Method 1b: Try ECDsaCertificateExtensions (ECDSA keys)
333+
if (-not $keyName) {
334+
try {
335+
Write-Host "Trying ECDsaCertificateExtensions.GetECDsaPrivateKey..."
336+
$ecdsaKey = [System.Security.Cryptography.X509Certificates.ECDsaCertificateExtensions]::GetECDsaPrivateKey($serverCert)
337+
if ($ecdsaKey) {
338+
Write-Host " Got ECDSA key object: $($ecdsaKey.GetType().FullName)"
339+
if ($ecdsaKey.Key) {
340+
$keyName = $ecdsaKey.Key.UniqueName
341+
Write-Host " ECDSA CNG key unique name: $keyName"
342+
}
343+
}
344+
} catch {
345+
Write-Host " ECDsaCertificateExtensions method failed: $_"
346+
}
347+
}
348+
314349
# Method 2: Try PrivateKey property (older API)
315350
if (-not $keyName) {
316351
try {

src/certman.c

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,10 +103,13 @@ int wolfSSH_SetCertManager(WOLFSSH_CTX* ctx, WOLFSSL_CERT_MANAGER* cm)
103103
}
104104

105105
/* free up existing cm if present */
106+
if (ctx->certMan->cm == cm) {
107+
return WS_SUCCESS;
108+
}
109+
wolfSSL_CertManager_up_ref(cm);
106110
if (ctx->certMan->cm != NULL) {
107111
wolfSSL_CertManagerFree(ctx->certMan->cm);
108112
}
109-
wolfSSL_CertManager_up_ref(cm);
110113
ctx->certMan->cm = cm;
111114

112115
return WS_SUCCESS;
@@ -638,6 +641,11 @@ int wolfSSH_ParseCertStoreSpec(const char* spec,
638641
wSubjectNameLen = MultiByteToWideChar(CP_UTF8, 0, subjectName, -1,
639642
NULL, 0);
640643

644+
if (wStoreNameLen == 0 || wSubjectNameLen == 0) {
645+
WFREE(specCopy, heap, DYNTYPE_TEMP);
646+
return WS_FATAL_ERROR;
647+
}
648+
641649
*wStoreName = (wchar_t*)WMALLOC(wStoreNameLen * sizeof(wchar_t),
642650
heap, DYNTYPE_TEMP);
643651
*wSubjectName = (wchar_t*)WMALLOC(wSubjectNameLen * sizeof(wchar_t),

src/internal.c

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1127,11 +1127,11 @@ void CtxResourceFree(WOLFSSH_CTX* ctx)
11271127
ctx->privateKey[i].certStoreContext = NULL;
11281128
}
11291129
if (ctx->privateKey[i].storeName != NULL) {
1130-
WFREE((void*)ctx->privateKey[i].storeName, ctx->heap, DYNTYPE_STRING);
1130+
WFREE(ctx->privateKey[i].storeName, ctx->heap, DYNTYPE_STRING);
11311131
ctx->privateKey[i].storeName = NULL;
11321132
}
11331133
if (ctx->privateKey[i].subjectName != NULL) {
1134-
WFREE((void*)ctx->privateKey[i].subjectName, ctx->heap, DYNTYPE_STRING);
1134+
WFREE(ctx->privateKey[i].subjectName, ctx->heap, DYNTYPE_STRING);
11351135
ctx->privateKey[i].subjectName = NULL;
11361136
}
11371137
ctx->privateKey[i].useCertStore = 0;
@@ -11369,6 +11369,47 @@ static int SendKexGetSigningKey(WOLFSSH* ssh,
1136911369
ret = wc_ecc_init_ex(&sigKeyBlock_ptr->sk.ecc.key, heap,
1137011370
INVALID_DEVID);
1137111371
scratch = 0;
11372+
#ifdef USE_WINDOWS_API
11373+
#ifdef WOLFSSH_CERTS
11374+
if (ret == 0 && ssh->ctx->privateKey[keyIdx].useCertStore) {
11375+
/* For cert store keys, extract the ECC public key from the
11376+
* DER certificate. Signing uses the cert store handle via
11377+
* SignHEcdsa's cert-store branch. */
11378+
const byte* certDer =
11379+
ssh->ctx->privateKey[keyIdx].cert;
11380+
word32 certDerSz =
11381+
ssh->ctx->privateKey[keyIdx].certSz;
11382+
11383+
if (certDer != NULL && certDerSz > 0) {
11384+
byte* pubKeyDer = NULL;
11385+
word32 pubKeyDerSz = 0;
11386+
11387+
ret = ExtractPubKeyDerFromCert(certDer, certDerSz,
11388+
&pubKeyDer, &pubKeyDerSz, heap);
11389+
if (ret == 0) {
11390+
word32 idx2 = 0;
11391+
ret = wc_EccPublicKeyDecode(pubKeyDer, &idx2,
11392+
&sigKeyBlock_ptr->sk.ecc.key, pubKeyDerSz);
11393+
}
11394+
if (pubKeyDer != NULL)
11395+
WFREE(pubKeyDer, heap, DYNTYPE_PUBKEY);
11396+
11397+
if (ret != 0) {
11398+
WLOG(WS_LOG_DEBUG,
11399+
"SendKexDhReply: cert store ECC pubkey "
11400+
"decode failed %d", ret);
11401+
ret = WS_CRYPTO_FAILED;
11402+
}
11403+
}
11404+
else {
11405+
WLOG(WS_LOG_DEBUG,
11406+
"SendKexDhReply: cert store key has no cert DER");
11407+
ret = WS_BAD_ARGUMENT;
11408+
}
11409+
}
11410+
else
11411+
#endif /* WOLFSSH_CERTS */
11412+
#endif /* USE_WINDOWS_API */
1137211413
if (ret == 0)
1137311414
ret = wc_EccPrivateKeyDecode(ssh->ctx->privateKey[keyIdx].key,
1137411415
&scratch, &sigKeyBlock_ptr->sk.ecc.key,
@@ -12655,8 +12696,19 @@ static int SignHEcdsa(WOLFSSH* ssh, byte* sig, word32* sigSz,
1265512696
/* NCryptSignHash for ECDSA returns raw r||s (each half of sigSz),
1265612697
* NOT DER-encoded. Split directly. */
1265712698
if (sigKey->pvtKey != NULL && sigKey->pvtKey->useCertStore) {
12658-
word32 halfSz = *sigSz / 2;
12699+
word32 halfSz;
1265912700
word32 rOff = 0, sOff = 0;
12701+
if (*sigSz < 2 || (*sigSz & 1) != 0) {
12702+
ret = WS_ECC_E;
12703+
}
12704+
halfSz = *sigSz / 2;
12705+
if (ret == WS_SUCCESS && (halfSz > rSz || halfSz > sSz)) {
12706+
ret = WS_ECC_E;
12707+
}
12708+
if (ret != WS_SUCCESS) {
12709+
/* skip the copy/trim below */
12710+
}
12711+
else {
1266012712
WMEMCPY(r, sig, halfSz);
1266112713
WMEMCPY(s, sig + halfSz, halfSz);
1266212714
/* Trim leading zeroes (use offset to preserve base pointer
@@ -12671,6 +12723,7 @@ static int SignHEcdsa(WOLFSSH* ssh, byte* sig, word32* sigSz,
1267112723
if (sOff > 0)
1267212724
WMEMMOVE(s, s + sOff, halfSz - sOff);
1267312725
sSz = halfSz - sOff;
12726+
}
1267412727
} else
1267512728
#endif /* WOLFSSH_CERTS */
1267612729
#endif /* USE_WINDOWS_API */

src/ssh.c

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2501,6 +2501,7 @@ int wolfSSH_CTX_UsePrivateKey_fromStore(WOLFSSH_CTX* ctx,
25012501
PCCERT_CONTEXT pCertContext = NULL;
25022502
word32 keyIdx = 0;
25032503
byte keyId = ID_NONE;
2504+
byte addedNewSlot = 0;
25042505
void* heap = NULL;
25052506

25062507
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_CTX_UsePrivateKey_fromStore()");
@@ -2624,6 +2625,7 @@ int wolfSSH_CTX_UsePrivateKey_fromStore(WOLFSSH_CTX* ctx,
26242625
if (keyIdx == WOLFSSH_MAX_PVT_KEYS && ctx->privateKeyCount < WOLFSSH_MAX_PVT_KEYS) {
26252626
keyIdx = ctx->privateKeyCount;
26262627
ctx->privateKeyCount++;
2628+
addedNewSlot = 1;
26272629
}
26282630
}
26292631

@@ -2634,6 +2636,19 @@ int wolfSSH_CTX_UsePrivateKey_fromStore(WOLFSSH_CTX* ctx,
26342636
return WS_MEMORY_E;
26352637
}
26362638

2639+
/* Free existing resources if replacing an existing slot */
2640+
if (ctx->privateKey[keyIdx].useCertStore) {
2641+
if (ctx->privateKey[keyIdx].certStoreContext != NULL)
2642+
CertFreeCertificateContext(
2643+
(PCCERT_CONTEXT)ctx->privateKey[keyIdx].certStoreContext);
2644+
if (ctx->privateKey[keyIdx].storeName != NULL)
2645+
WFREE(ctx->privateKey[keyIdx].storeName, heap, DYNTYPE_STRING);
2646+
if (ctx->privateKey[keyIdx].subjectName != NULL)
2647+
WFREE(ctx->privateKey[keyIdx].subjectName, heap, DYNTYPE_STRING);
2648+
if (ctx->privateKey[keyIdx].cert != NULL)
2649+
WFREE(ctx->privateKey[keyIdx].cert, heap, DYNTYPE_CERT);
2650+
}
2651+
26372652
/* Set up the private key structure */
26382653
ctx->privateKey[keyIdx].publicKeyFmt = keyId;
26392654
ctx->privateKey[keyIdx].useCertStore = 1;
@@ -2668,8 +2683,8 @@ int wolfSSH_CTX_UsePrivateKey_fromStore(WOLFSSH_CTX* ctx,
26682683
byte* certBuf = (byte*)WMALLOC(certSz, heap, DYNTYPE_CERT);
26692684
if (certBuf == NULL) {
26702685
/* Cleanup */
2671-
WFREE((void*)ctx->privateKey[keyIdx].storeName, heap, DYNTYPE_STRING);
2672-
WFREE((void*)ctx->privateKey[keyIdx].subjectName, heap, DYNTYPE_STRING);
2686+
WFREE(ctx->privateKey[keyIdx].storeName, heap, DYNTYPE_STRING);
2687+
WFREE(ctx->privateKey[keyIdx].subjectName, heap, DYNTYPE_STRING);
26732688
CertFreeCertificateContext(pCertContext);
26742689
CertCloseStore(hStore, 0);
26752690
WLOG(WS_LOG_DEBUG, "wolfSSH_CTX_UsePrivateKey_fromStore: Certificate buffer allocation failed");
@@ -2697,8 +2712,8 @@ int wolfSSH_CTX_UsePrivateKey_fromStore(WOLFSSH_CTX* ctx,
26972712
"access private key, error: %lu. Check that the current user "
26982713
"or service account has permission to access the key.", dwErr);
26992714
/* Cleanup already stored data */
2700-
WFREE((void*)ctx->privateKey[keyIdx].storeName, heap, DYNTYPE_STRING);
2701-
WFREE((void*)ctx->privateKey[keyIdx].subjectName, heap, DYNTYPE_STRING);
2715+
WFREE(ctx->privateKey[keyIdx].storeName, heap, DYNTYPE_STRING);
2716+
WFREE(ctx->privateKey[keyIdx].subjectName, heap, DYNTYPE_STRING);
27022717
WFREE(ctx->privateKey[keyIdx].cert, heap, DYNTYPE_CERT);
27032718
ctx->privateKey[keyIdx].useCertStore = 0;
27042719
CertFreeCertificateContext(pCertContext);
@@ -2707,7 +2722,8 @@ int wolfSSH_CTX_UsePrivateKey_fromStore(WOLFSSH_CTX* ctx,
27072722
ctx->privateKey[keyIdx].subjectName = NULL;
27082723
ctx->privateKey[keyIdx].cert = NULL;
27092724
ctx->privateKey[keyIdx].certSz = 0;
2710-
ctx->privateKeyCount--;
2725+
if (addedNewSlot)
2726+
ctx->privateKeyCount--;
27112727
CertCloseStore(hStore, 0);
27122728
return WS_CRYPTO_FAILED;
27132729
}

wolfssh/internal.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -546,9 +546,9 @@ typedef struct WOLFSSH_PVT_KEY {
546546
void* certStoreContext;
547547
/* Windows certificate context (PCCERT_CONTEXT) for MS Certificate Store.
548548
* Owned by CTX, must be freed with CertFreeCertificateContext. */
549-
const wchar_t* storeName;
549+
wchar_t* storeName;
550550
/* Certificate store name (e.g., "My", "Root"). Owned by CTX. */
551-
const wchar_t* subjectName;
551+
wchar_t* subjectName;
552552
/* Certificate subject name or thumbprint for lookup. Owned by CTX. */
553553
DWORD dwFlags;
554554
/* Certificate store flags (e.g., CERT_SYSTEM_STORE_CURRENT_USER). */

0 commit comments

Comments
 (0)