123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409 |
- 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.
|