Jordan Russell 6d3269836c
ECDSA: Load bcrypt.dll dynamically.
Just to ensure it's loaded from System32, and never from the application directory.
2025-04-13 03:23:25 -05:00

410 lines
12 KiB
ObjectPascal

unit ECDSA;
{
Inno Setup
Copyright (C) 1997-2025 Jordan Russell
Portions by Martijn Laan
For conditions of distribution and use, see LICENSE.TXT.
ECDSA-P256 signing, verification, and key generation, based on CNG (BCrypt)
}
interface
uses
Windows, SysUtils;
type
TECDSAInt256 = array[0..31] of Byte;
TECDSAPublicKey = packed record
Public_x: TECDSAInt256;
Public_y: TECDSAInt256;
procedure Clear;
end;
TECDSAPrivateKey = packed record
PublicKey: TECDSAPublicKey;
Private_d: TECDSAInt256;
procedure Clear;
end;
TECDSASignature = packed record
Sig_r: TECDSAInt256;
Sig_s: TECDSAInt256;
procedure Clear;
end;
TECDSAKey = class
private
FAlgorithmHandle: THandle; { BCRYPT_ALG_HANDLE }
FKeyHandle: THandle; { BCRYPT_KEY_HANDLE }
class procedure CheckStatus(const AFunctionName: String;
const AStatus: NTSTATUS); static;
procedure KeyHandleRequired;
public
constructor Create;
destructor Destroy; override;
procedure DestroyKey;
procedure ExportPrivateKey(out APrivateKey: TECDSAPrivateKey);
procedure ExportPublicKey(out APublicKey: TECDSAPublicKey);
procedure GenerateKeyPair;
procedure ImportPrivateKey([ref] const APrivateKey: TECDSAPrivateKey);
procedure ImportPublicKey([ref] const APublicKey: TECDSAPublicKey);
procedure SignHash(const AHash: array of Byte;
out ASignature: TECDSASignature);
function VerifySignature(const AHash: array of Byte;
const ASignature: TECDSASignature): Boolean;
end;
EECDSAError = class(Exception);
implementation
type
BCRYPT_ALG_HANDLE = type THandle;
BCRYPT_KEY_HANDLE = type THandle;
BCRYPT_ECCKEY_BLOB = record
dwMagic: ULONG;
cbKey: ULONG;
end;
const
BCRYPT_ECDSA_P256_ALGORITHM = 'ECDSA_P256';
BCRYPT_ECCPRIVATE_BLOB = 'ECCPRIVATEBLOB';
BCRYPT_ECCPUBLIC_BLOB = 'ECCPUBLICBLOB';
BCRYPT_ECDSA_PRIVATE_P256_MAGIC = $32534345;
BCRYPT_ECDSA_PUBLIC_P256_MAGIC = $31534345;
STATUS_INVALID_SIGNATURE = NTSTATUS($C000A000);
var
BCryptFunctionsInitialized: BOOL;
BCryptCloseAlgorithmProvider: function(hAlgorithm: BCRYPT_ALG_HANDLE;
dwFlags: ULONG): NTSTATUS;
stdcall;
BCryptDestroyKey: function(hKey: BCRYPT_KEY_HANDLE): NTSTATUS;
stdcall;
BCryptExportKey: function(hKey: BCRYPT_KEY_HANDLE; hExportKey: BCRYPT_KEY_HANDLE;
pszBlobType: LPCWSTR; var pbOutput; cbOutput: ULONG; out pcbResult: ULONG;
dwFlags: ULONG): NTSTATUS;
stdcall;
BCryptFinalizeKeyPair: function(hKey: BCRYPT_KEY_HANDLE; dwFlags: ULONG): NTSTATUS;
stdcall;
BCryptGenerateKeyPair: function(hAlgorithm: BCRYPT_ALG_HANDLE;
out phKey: BCRYPT_KEY_HANDLE; dwLength: ULONG; dwFlags: ULONG): NTSTATUS;
stdcall;
BCryptImportKeyPair: function(hAlgorithm: BCRYPT_ALG_HANDLE;
hImportKey: BCRYPT_KEY_HANDLE; pszBlobType: LPCWSTR;
out phKey: BCRYPT_KEY_HANDLE; const pbInput; cbInput: ULONG;
dwFlags: ULONG): NTSTATUS;
stdcall;
BCryptOpenAlgorithmProvider: function(out phAlgorithm: BCRYPT_ALG_HANDLE;
pszAlgId: LPCWSTR; pszImplementation: LPCWSTR; dwFlags: ULONG): NTSTATUS;
stdcall;
BCryptSignHash: function(hKey: BCRYPT_KEY_HANDLE; pPaddingInfo: Pointer;
const pbInput; cbInput: ULONG; var pbOutput; cbOutput: ULONG;
out pcbResult: ULONG; dwFlags: ULONG): NTSTATUS;
stdcall;
BCryptVerifySignature: function(hKey: BCRYPT_KEY_HANDLE; pPaddingInfo: Pointer;
const pbHash; cbHash: ULONG; const pbSignature; cbSignature: ULONG;
dwFlags: ULONG): NTSTATUS;
stdcall;
type
{ ECDSA-P256 key blob formats specific to BCrypt }
TBCryptPrivateKeyBlob = record
Header: BCRYPT_ECCKEY_BLOB;
Public_x: TECDSAInt256;
Public_y: TECDSAInt256;
Private_d: TECDSAInt256;
procedure Clear;
end;
TBCryptPublicKeyBlob = record
Header: BCRYPT_ECCKEY_BLOB;
Public_x: TECDSAInt256;
Public_y: TECDSAInt256;
procedure Clear;
end;
procedure InitBCryptFunctions;
var
M: HMODULE;
procedure InitFunc(out AProc: Pointer; const AProcName: PAnsiChar);
begin
AProc := GetProcAddress(M, AProcName);
if not Assigned(AProc) then
raise EECDSAError.CreateFmt('Failed to get address of %s in bcrypt.dll',
[AProcName]);
end;
var
SystemDir: array[0..MAX_PATH-1] of Char;
begin
if BCryptFunctionsInitialized then begin
MemoryBarrier;
Exit;
end;
GetSystemDirectory(SystemDir, SizeOf(SystemDir) div SizeOf(SystemDir[0]));
M := LoadLibrary(PChar(SystemDir + '\bcrypt.dll'));
if M = 0 then
raise EECDSAError.Create('Failed to load bcrypt.dll');
InitFunc(@BCryptCloseAlgorithmProvider, 'BCryptCloseAlgorithmProvider');
InitFunc(@BCryptDestroyKey, 'BCryptDestroyKey');
InitFunc(@BCryptExportKey, 'BCryptExportKey');
InitFunc(@BCryptFinalizeKeyPair, 'BCryptFinalizeKeyPair');
InitFunc(@BCryptGenerateKeyPair, 'BCryptGenerateKeyPair');
InitFunc(@BCryptImportKeyPair, 'BCryptImportKeyPair');
InitFunc(@BCryptOpenAlgorithmProvider, 'BCryptOpenAlgorithmProvider');
InitFunc(@BCryptSignHash, 'BCryptSignHash');
InitFunc(@BCryptVerifySignature, 'BCryptVerifySignature');
MemoryBarrier;
BCryptFunctionsInitialized := True;
end;
{ TBCryptPrivateKeyBlob }
procedure TBCryptPrivateKeyBlob.Clear;
begin
FillChar(Self, SizeOf(Self), 0);
end;
{ TBCryptPublicKeyBlob }
procedure TBCryptPublicKeyBlob.Clear;
begin
FillChar(Self, SizeOf(Self), 0);
end;
{ TECDSAPublicKey }
procedure TECDSAPublicKey.Clear;
begin
FillChar(Self, SizeOf(Self), 0);
end;
{ TECDSAPrivateKey }
procedure TECDSAPrivateKey.Clear;
begin
FillChar(Self, SizeOf(Self), 0);
end;
{ TECDSASignature }
procedure TECDSASignature.Clear;
begin
FillChar(Self, SizeOf(Self), 0);
end;
{ TECDSAKey }
constructor TECDSAKey.Create;
begin
inherited;
InitBCryptFunctions;
var LAlgorithmHandle: BCRYPT_ALG_HANDLE;
CheckStatus('BCryptOpenAlgorithmProvider',
BCryptOpenAlgorithmProvider(LAlgorithmHandle, BCRYPT_ECDSA_P256_ALGORITHM,
nil, 0));
FAlgorithmHandle := LAlgorithmHandle; { assign only on success }
end;
destructor TECDSAKey.Destroy;
begin
DestroyKey;
if FAlgorithmHandle <> 0 then
BCryptCloseAlgorithmProvider(FAlgorithmHandle, 0);
inherited;
end;
class procedure TECDSAKey.CheckStatus(const AFunctionName: String;
const AStatus: NTSTATUS);
begin
if AStatus <> 0 then
raise EECDSAError.CreateFmt('%s failed with error code 0x%x',
[AFunctionName, AStatus]);
end;
procedure TECDSAKey.DestroyKey;
begin
const H = FKeyHandle;
if H <> 0 then begin
FKeyHandle := 0;
BCryptDestroyKey(H);
end;
end;
procedure TECDSAKey.ExportPrivateKey(out APrivateKey: TECDSAPrivateKey);
begin
KeyHandleRequired;
var KeyBlob: TBCryptPrivateKeyBlob;
{ Initially clear KeyBlob just to make it easier to verify that
BCryptExportKey overwrites the entire record }
KeyBlob.Clear;
try
var ResultSize: ULONG;
CheckStatus('BCryptExportKey',
BCryptExportKey(FKeyHandle, 0, BCRYPT_ECCPRIVATE_BLOB, KeyBlob,
SizeOf(KeyBlob), ResultSize, 0));
if ResultSize <> SizeOf(KeyBlob) then
raise EECDSAError.Create('BCryptExportKey result invalid (1)');
if KeyBlob.Header.dwMagic <> BCRYPT_ECDSA_PRIVATE_P256_MAGIC then
raise EECDSAError.Create('BCryptExportKey result invalid (2)');
if KeyBlob.Header.cbKey <> SizeOf(KeyBlob.Public_x) then
raise EECDSAError.Create('BCryptExportKey result invalid (3)');
APrivateKey.PublicKey.Public_x := KeyBlob.Public_x;
APrivateKey.PublicKey.Public_y := KeyBlob.Public_y;
APrivateKey.Private_d := KeyBlob.Private_d;
finally
{ Security: don't leave copy of private key on the stack }
KeyBlob.Clear;
end;
end;
procedure TECDSAKey.ExportPublicKey(out APublicKey: TECDSAPublicKey);
begin
KeyHandleRequired;
var KeyBlob: TBCryptPublicKeyBlob;
{ Initially clear KeyBlob just to make it easier to verify that
BCryptExportKey overwrites the entire record }
KeyBlob.Clear;
try
var ResultSize: ULONG;
CheckStatus('BCryptExportKey',
BCryptExportKey(FKeyHandle, 0, BCRYPT_ECCPUBLIC_BLOB, KeyBlob,
SizeOf(KeyBlob), ResultSize, 0));
if ResultSize <> SizeOf(KeyBlob) then
raise EECDSAError.Create('BCryptExportKey result invalid (1)');
if KeyBlob.Header.dwMagic <> BCRYPT_ECDSA_PUBLIC_P256_MAGIC then
raise EECDSAError.Create('BCryptExportKey result invalid (2)');
if KeyBlob.Header.cbKey <> SizeOf(KeyBlob.Public_x) then
raise EECDSAError.Create('BCryptExportKey result invalid (3)');
APublicKey.Public_x := KeyBlob.Public_x;
APublicKey.Public_y := KeyBlob.Public_y;
finally
{ There's no private key, but clear anyway for consistency }
KeyBlob.Clear;
end;
end;
procedure TECDSAKey.GenerateKeyPair;
begin
DestroyKey;
var LKeyHandle: BCRYPT_KEY_HANDLE;
CheckStatus('BCryptGenerateKeyPair',
BCryptGenerateKeyPair(FAlgorithmHandle, LKeyHandle, 256, 0));
try
CheckStatus('BCryptFinalizeKeyPair',
BCryptFinalizeKeyPair(LKeyHandle, 0));
except
BCryptDestroyKey(LKeyHandle);
raise;
end;
FKeyHandle := LKeyHandle; { assign only on success }
end;
procedure TECDSAKey.ImportPrivateKey([ref] const APrivateKey: TECDSAPrivateKey);
begin
DestroyKey;
var KeyBlob: TBCryptPrivateKeyBlob;
try
KeyBlob.Header.dwMagic := BCRYPT_ECDSA_PRIVATE_P256_MAGIC;
KeyBlob.Header.cbKey := SizeOf(KeyBlob.Public_x);
KeyBlob.Public_x := APrivateKey.PublicKey.Public_x;
KeyBlob.Public_y := APrivateKey.PublicKey.Public_y;
KeyBlob.Private_d := APrivateKey.Private_d;
var LKeyHandle: BCRYPT_KEY_HANDLE;
CheckStatus('BCryptImportKeyPair',
BCryptImportKeyPair(FAlgorithmHandle, 0, BCRYPT_ECCPRIVATE_BLOB,
LKeyHandle, KeyBlob, SizeOf(KeyBlob), 0));
FKeyHandle := LKeyHandle; { assign only on success }
finally
{ Security: don't leave copy of private key on the stack }
KeyBlob.Clear;
end;
end;
procedure TECDSAKey.ImportPublicKey([ref] const APublicKey: TECDSAPublicKey);
begin
DestroyKey;
var KeyBlob: TBCryptPublicKeyBlob;
try
KeyBlob.Header.dwMagic := BCRYPT_ECDSA_PUBLIC_P256_MAGIC;
KeyBlob.Header.cbKey := SizeOf(KeyBlob.Public_x);
KeyBlob.Public_x := APublicKey.Public_x;
KeyBlob.Public_y := APublicKey.Public_y;
var LKeyHandle: BCRYPT_KEY_HANDLE;
CheckStatus('BCryptImportKeyPair',
BCryptImportKeyPair(FAlgorithmHandle, 0, BCRYPT_ECCPUBLIC_BLOB,
LKeyHandle, KeyBlob, SizeOf(KeyBlob), 0));
FKeyHandle := LKeyHandle; { assign only on success }
finally
{ There's no private key, but clear anyway for consistency }
KeyBlob.Clear;
end;
end;
procedure TECDSAKey.KeyHandleRequired;
begin
if FKeyHandle = 0 then
raise EECDSAError.Create('No key has been assigned');
end;
procedure TECDSAKey.SignHash(const AHash: array of Byte;
out ASignature: TECDSASignature);
begin
KeyHandleRequired;
{ Initially clear ASignature just to make it easier to verify that
BCryptSignHash overwrites the entire record }
ASignature.Clear;
var ResultSize: ULONG;
CheckStatus('BCryptSignHash',
BCryptSignHash(FKeyHandle, nil, AHash[0], ULONG(Length(AHash)), ASignature,
SizeOf(ASignature), ResultSize, 0));
if ResultSize <> SizeOf(ASignature) then
raise EECDSAError.Create('BCryptSignHash result size invalid');
end;
function TECDSAKey.VerifySignature(const AHash: array of Byte;
const ASignature: TECDSASignature): Boolean;
begin
KeyHandleRequired;
const Status = BCryptVerifySignature(FKeyHandle, nil, AHash[0],
ULONG(Length(AHash)), ASignature, SizeOf(ASignature), 0);
if Status = STATUS_INVALID_SIGNATURE then
Result := False
else begin
CheckStatus('BCryptVerifySignature', Status);
Result := True;
end;
end;
end.