410 lines
12 KiB
ObjectPascal
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.
|