mirror of
https://github.com/polhenarejos/pico-keys-sdk
synced 2026-06-03 03:43:00 +02:00
Add PIN protection for TPM in windows and linux.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
This commit is contained in:
@@ -25,6 +25,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <io.h>
|
||||
|
||||
#include "mbedtls/bignum.h"
|
||||
#include "mbedtls/ecp.h"
|
||||
@@ -62,6 +63,168 @@ static const char *ncrypt_status_hint(SECURITY_STATUS st) {
|
||||
}
|
||||
}
|
||||
|
||||
static int read_tpm_pin_prompt(const char *prompt, char *pin_out, size_t pin_out_size) {
|
||||
const char *pin_env = getenv("PICO_NOVUS_TPM_PIN");
|
||||
if (!pin_out || pin_out_size < 2) {
|
||||
return -1;
|
||||
}
|
||||
if (pin_env && pin_env[0] != '\0') {
|
||||
size_t n = strlen(pin_env);
|
||||
if (n >= pin_out_size) {
|
||||
return -1;
|
||||
}
|
||||
memcpy(pin_out, pin_env, n + 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!_isatty(_fileno(stdin))) {
|
||||
fprintf(stderr, "[win-tpm] No TTY for PIN prompt; set PICO_NOVUS_TPM_PIN\n");
|
||||
return -1;
|
||||
}
|
||||
HANDLE h_in = GetStdHandle(STD_INPUT_HANDLE);
|
||||
DWORD old_mode = 0;
|
||||
DWORD new_mode = 0;
|
||||
|
||||
if (h_in == INVALID_HANDLE_VALUE || !GetConsoleMode(h_in, &old_mode)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
new_mode = old_mode & (~ENABLE_ECHO_INPUT);
|
||||
fprintf(stderr, "%s", prompt ? prompt : "Enter TPM key PIN: ");
|
||||
fflush(stderr);
|
||||
|
||||
if (!SetConsoleMode(h_in, new_mode)) {
|
||||
return -1;
|
||||
}
|
||||
if (!fgets(pin_out, (int)pin_out_size, stdin)) {
|
||||
SetConsoleMode(h_in, old_mode);
|
||||
fprintf(stderr, "\n");
|
||||
return -1;
|
||||
}
|
||||
SetConsoleMode(h_in, old_mode);
|
||||
fprintf(stderr, "\n");
|
||||
size_t n = strlen(pin_out);
|
||||
if (n > 0 && pin_out[n - 1] == '\n') {
|
||||
pin_out[n - 1] = '\0';
|
||||
}
|
||||
if (n > 1 && pin_out[n - 2] == '\r') {
|
||||
pin_out[n - 2] = '\0';
|
||||
}
|
||||
return (pin_out[0] != '\0') ? 0 : -1;
|
||||
}
|
||||
|
||||
static int key_exists_by_name(NCRYPT_PROV_HANDLE prov, LPCWSTR key_name, int *exists_out) {
|
||||
SECURITY_STATUS st;
|
||||
NCRYPT_KEY_HANDLE key = 0;
|
||||
|
||||
if (!exists_out) {
|
||||
return -1;
|
||||
}
|
||||
*exists_out = 0;
|
||||
|
||||
st = NCryptOpenKey(prov, &key, key_name, 0, 0);
|
||||
if (st == ERROR_SUCCESS) {
|
||||
*exists_out = 1;
|
||||
NCryptFreeObject(key);
|
||||
return 0;
|
||||
}
|
||||
if (st == NTE_BAD_KEYSET) {
|
||||
return 0;
|
||||
}
|
||||
return win_err("NCryptOpenKey(exists)", st);
|
||||
}
|
||||
|
||||
static int delete_persisted_key_if_exists(NCRYPT_PROV_HANDLE prov, LPCWSTR key_name) {
|
||||
SECURITY_STATUS st;
|
||||
NCRYPT_KEY_HANDLE key = 0;
|
||||
|
||||
st = NCryptOpenKey(prov, &key, key_name, 0, 0);
|
||||
if (st == NTE_BAD_KEYSET) {
|
||||
return 0;
|
||||
}
|
||||
if (st != ERROR_SUCCESS) {
|
||||
return win_err("NCryptOpenKey(delete)", st);
|
||||
}
|
||||
|
||||
st = NCryptDeleteKey(key, 0);
|
||||
if (st != ERROR_SUCCESS) {
|
||||
NCryptFreeObject(key);
|
||||
return win_err("NCryptDeleteKey", st);
|
||||
}
|
||||
|
||||
printf("[win-tpm] Deleted key '%ls'\n", key_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int set_tpm_usage_pin(NCRYPT_KEY_HANDLE key, const char *pin, const char *ctx) {
|
||||
SECURITY_STATUS st;
|
||||
int wlen;
|
||||
wchar_t wpin[128];
|
||||
|
||||
if (!pin || pin[0] == '\0') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
memset(wpin, 0, sizeof(wpin));
|
||||
wlen = MultiByteToWideChar(CP_UTF8, 0, pin, -1, wpin, (int)(sizeof(wpin) / sizeof(wpin[0])));
|
||||
if (wlen <= 1) {
|
||||
fprintf(stderr, "[win-tpm] invalid PIN encoding\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* PCP usage auth is expected as WCHAR string (including terminator). */
|
||||
st = NCryptSetProperty(
|
||||
key,
|
||||
NCRYPT_PCP_USAGEAUTH_PROPERTY,
|
||||
(PBYTE)wpin,
|
||||
(DWORD)(wlen * sizeof(wchar_t)),
|
||||
0
|
||||
);
|
||||
SecureZeroMemory(wpin, sizeof(wpin));
|
||||
if (st != ERROR_SUCCESS) {
|
||||
if (st == NTE_NOT_SUPPORTED) {
|
||||
fprintf(stderr, "[win-tpm] TPM provider does not support PIN auth property on this machine\n");
|
||||
return 1;
|
||||
}
|
||||
return win_err(ctx, st);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int probe_tpm_native_pin_support(NCRYPT_PROV_HANDLE prov) {
|
||||
SECURITY_STATUS st;
|
||||
NCRYPT_KEY_HANDLE probe_key = 0;
|
||||
const wchar_t probe_name[] = L"pico_novus_pin_probe_tmp";
|
||||
const wchar_t probe_pin[] = L"0";
|
||||
|
||||
st = NCryptCreatePersistedKey(prov, &probe_key, NCRYPT_ECDH_P256_ALGORITHM, probe_name, 0, NCRYPT_OVERWRITE_KEY_FLAG);
|
||||
if (st != ERROR_SUCCESS) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
st = NCryptSetProperty(
|
||||
probe_key,
|
||||
NCRYPT_PCP_USAGEAUTH_PROPERTY,
|
||||
(PBYTE)probe_pin,
|
||||
(DWORD)(sizeof(probe_pin)),
|
||||
0
|
||||
);
|
||||
if (st == NTE_NOT_SUPPORTED) {
|
||||
NCryptDeleteKey(probe_key, 0);
|
||||
return 0;
|
||||
}
|
||||
if (st != ERROR_SUCCESS) {
|
||||
NCryptDeleteKey(probe_key, 0);
|
||||
return -1;
|
||||
}
|
||||
|
||||
st = NCryptDeleteKey(probe_key, 0);
|
||||
if (st != ERROR_SUCCESS) {
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int derive_secp256k1_privkey_from_secret(const uint8_t *secret, size_t secret_len, uint8_t out_key32[32]) {
|
||||
int rc = -1;
|
||||
uint8_t digest[32];
|
||||
@@ -116,14 +279,22 @@ cleanup:
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int open_or_create_persisted_ecdh_p256_key(NCRYPT_PROV_HANDLE prov, LPCWSTR key_name, NCRYPT_KEY_HANDLE *out_key) {
|
||||
static int open_or_create_persisted_ecdh_p256_key(NCRYPT_PROV_HANDLE prov, LPCWSTR key_name, const char *pin, NCRYPT_KEY_HANDLE *out_key, int *created_out) {
|
||||
SECURITY_STATUS st;
|
||||
NCRYPT_KEY_HANDLE key = 0;
|
||||
DWORD pin_len = 0;
|
||||
int pin_rc = 0;
|
||||
|
||||
if (!out_key) {
|
||||
return -1;
|
||||
}
|
||||
*out_key = 0;
|
||||
if (created_out) {
|
||||
*created_out = 0;
|
||||
}
|
||||
if (pin && pin[0] != '\0') {
|
||||
pin_len = (DWORD)strlen(pin);
|
||||
}
|
||||
|
||||
st = NCryptOpenKey(prov, &key, key_name, 0, 0);
|
||||
if (st == NTE_BAD_KEYSET) {
|
||||
@@ -131,12 +302,25 @@ static int open_or_create_persisted_ecdh_p256_key(NCRYPT_PROV_HANDLE prov, LPCWS
|
||||
if (st != ERROR_SUCCESS) {
|
||||
return win_err("NCryptCreatePersistedKey", st);
|
||||
}
|
||||
if (pin_len > 0) {
|
||||
pin_rc = set_tpm_usage_pin(key, pin, "NCryptSetProperty(NCRYPT_PCP_USAGEAUTH_PROPERTY/create)");
|
||||
if (pin_rc == 1) {
|
||||
fprintf(stderr, "[win-tpm] PIN protection unsupported by TPM provider; continuing without TPM-native PIN\n");
|
||||
}
|
||||
else if (pin_rc != 0) {
|
||||
NCryptFreeObject(key);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
st = NCryptFinalizeKey(key, 0);
|
||||
if (st != ERROR_SUCCESS) {
|
||||
NCryptFreeObject(key);
|
||||
return win_err("NCryptFinalizeKey", st);
|
||||
}
|
||||
printf("[win-tpm] Created key '%ls'\n", key_name);
|
||||
if (created_out) {
|
||||
*created_out = 1;
|
||||
}
|
||||
}
|
||||
else if (st != ERROR_SUCCESS) {
|
||||
return win_err("NCryptOpenKey", st);
|
||||
@@ -145,6 +329,17 @@ static int open_or_create_persisted_ecdh_p256_key(NCRYPT_PROV_HANDLE prov, LPCWS
|
||||
printf("[win-tpm] Using existing key '%ls'\n", key_name);
|
||||
}
|
||||
|
||||
if (pin_len > 0) {
|
||||
pin_rc = set_tpm_usage_pin(key, pin, "NCryptSetProperty(NCRYPT_PCP_USAGEAUTH_PROPERTY/open)");
|
||||
if (pin_rc == 1) {
|
||||
fprintf(stderr, "[win-tpm] PIN protection unsupported by TPM provider; continuing without TPM-native PIN\n");
|
||||
}
|
||||
else if (pin_rc != 0) {
|
||||
NCryptFreeObject(key);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
*out_key = key;
|
||||
return 0;
|
||||
}
|
||||
@@ -162,6 +357,9 @@ static int windows_tpm_vault_load_or_create_key(uint8_t out_key32[32]) {
|
||||
DWORD raw_secret_len = 0;
|
||||
PBYTE peer_pub_blob = NULL;
|
||||
DWORD peer_pub_blob_len = 0;
|
||||
char pin[128] = {0};
|
||||
int key_exists = 0;
|
||||
int pin_supported = 0;
|
||||
|
||||
st = NCryptOpenStorageProvider(&tpm_prov, MS_PLATFORM_CRYPTO_PROVIDER, 0);
|
||||
if (st != ERROR_SUCCESS) {
|
||||
@@ -174,10 +372,10 @@ static int windows_tpm_vault_load_or_create_key(uint8_t out_key32[32]) {
|
||||
return win_err("NCryptOpenStorageProvider(MS_KEY_STORAGE_PROVIDER)", st);
|
||||
}
|
||||
printf("[win-tpm] Falling back to software KSP (MS_KEY_STORAGE_PROVIDER)\n");
|
||||
if (open_or_create_persisted_ecdh_p256_key(sw_prov, SW_LOCAL_NAME, &tpm_key) != 0) {
|
||||
if (open_or_create_persisted_ecdh_p256_key(sw_prov, SW_LOCAL_NAME, NULL, &tpm_key, NULL) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (open_or_create_persisted_ecdh_p256_key(sw_prov, SW_PEER_NAME, &sw_peer_key) != 0) {
|
||||
if (open_or_create_persisted_ecdh_p256_key(sw_prov, SW_PEER_NAME, NULL, &sw_peer_key, NULL) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
st = NCryptSecretAgreement(tpm_key, sw_peer_key, &secret, 0);
|
||||
@@ -218,10 +416,47 @@ static int windows_tpm_vault_load_or_create_key(uint8_t out_key32[32]) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (open_or_create_persisted_ecdh_p256_key(tpm_prov, TPM_KEY_NAME, &tpm_key) != 0) {
|
||||
pin_supported = probe_tpm_native_pin_support(tpm_prov);
|
||||
if (pin_supported < 0) {
|
||||
fprintf(stderr, "[win-tpm] Cannot determine TPM-native PIN support; continuing without PIN\n");
|
||||
pin_supported = 0;
|
||||
}
|
||||
else if (pin_supported == 0) {
|
||||
fprintf(stderr, "[win-tpm] TPM provider does not support native PIN auth; continuing without PIN\n");
|
||||
}
|
||||
|
||||
if (key_exists_by_name(tpm_prov, TPM_KEY_NAME, &key_exists) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (open_or_create_persisted_ecdh_p256_key(sw_prov, SW_PEER_NAME, &sw_peer_key) != 0) {
|
||||
if (pin_supported) {
|
||||
if (!key_exists) {
|
||||
if (read_tpm_pin_prompt("Set TPM key PIN: ", pin, sizeof(pin)) != 0) {
|
||||
fprintf(stderr, "[win-tpm] PIN is required to provision TPM key\n");
|
||||
goto cleanup;
|
||||
}
|
||||
if (!getenv("PICO_NOVUS_TPM_PIN") && _isatty(_fileno(stdin))) {
|
||||
char confirm[128] = {0};
|
||||
if (read_tpm_pin_prompt("Confirm TPM key PIN: ", confirm, sizeof(confirm)) != 0 || strcmp(pin, confirm) != 0) {
|
||||
fprintf(stderr, "[win-tpm] PIN confirmation mismatch\n");
|
||||
SecureZeroMemory(confirm, sizeof(confirm));
|
||||
goto cleanup;
|
||||
}
|
||||
SecureZeroMemory(confirm, sizeof(confirm));
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (read_tpm_pin_prompt("Enter TPM key PIN: ", pin, sizeof(pin)) != 0) {
|
||||
fprintf(stderr, "[win-tpm] PIN is required to unlock TPM key\n");
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (open_or_create_persisted_ecdh_p256_key(tpm_prov, TPM_KEY_NAME, pin_supported ? pin : NULL, &tpm_key, NULL) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (open_or_create_persisted_ecdh_p256_key(sw_prov, SW_PEER_NAME, NULL, &sw_peer_key, NULL) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
@@ -279,6 +514,7 @@ static int windows_tpm_vault_load_or_create_key(uint8_t out_key32[32]) {
|
||||
rc = 0;
|
||||
|
||||
cleanup:
|
||||
SecureZeroMemory(pin, sizeof(pin));
|
||||
if (peer_pub_blob) {
|
||||
SecureZeroMemory(peer_pub_blob, peer_pub_blob_len);
|
||||
free(peer_pub_blob);
|
||||
|
||||
Reference in New Issue
Block a user