500 lines
18 KiB
ObjectPascal
500 lines
18 KiB
ObjectPascal
unit ISSigFunc;
|
|
|
|
{
|
|
Inno Setup
|
|
Copyright (C) 1997-2025 Jordan Russell
|
|
Portions by Martijn Laan
|
|
For conditions of distribution and use, see LICENSE.TXT.
|
|
|
|
Functions for creating/verifying .issig signatures and importing/exporting
|
|
text-based keys
|
|
}
|
|
|
|
interface
|
|
|
|
uses
|
|
Windows, SysUtils, Classes, ECDSA, SHA256;
|
|
|
|
type
|
|
TISSigVerifySignatureResult = (vsrSuccess, vsrMalformed, vsrKeyNotFound,
|
|
vsrBad);
|
|
TISSigImportKeyResult = (ikrSuccess, ikrMalformed, ikrNotPrivateKey);
|
|
TISSigVerifySignatureFileMissingErrorProc = reference to procedure(const Filename: String);
|
|
TISSigVerifySignatureSigFileMissingErrorProc = reference to procedure(const Filename, SigFilename: String);
|
|
TISSigVerifySignatureVerificationFailedErrorProc = reference to procedure(const Filename, SigFilename: String; const VerifyResult: TISSigVerifySignatureResult);
|
|
|
|
{ Preferred, hardened functions for loading/saving .issig and key file text }
|
|
function ISSigLoadTextFromFile(const AFilename: String): String;
|
|
procedure ISSigSaveTextToFile(const AFilename, AText: String);
|
|
|
|
function ISSigCreateSignatureText(const AKey: TECDSAKey;
|
|
const AFileSize: Int64; const AFileHash: TSHA256Digest): String;
|
|
function ISSigVerifySignatureText(const AAllowedKeys: array of TECDSAKey;
|
|
const AText: String; out AFileSize: Int64;
|
|
out AFileHash: TSHA256Digest): TISSigVerifySignatureResult; overload;
|
|
function ISSigVerifySignatureText(const AAllowedKeys: array of TECDSAKey;
|
|
const AText: String; out AFileSize: Int64;
|
|
out AFileHash: TSHA256Digest; out AKeyUsedID: String): TISSigVerifySignatureResult; overload;
|
|
|
|
function ISSigVerifySignature(const AFilename: String; const AAllowedKeys: array of TECDSAKey;
|
|
out AExpectedFileSize: Int64; out AExpectedFileHash: TSHA256Digest; out AKeyUsedID: String;
|
|
const AFileMissingErrorProc: TISSigVerifySignatureFileMissingErrorProc;
|
|
const ASigFileMissingErrorProc: TISSigVerifySignatureSigFileMissingErrorProc;
|
|
const AVerificationFailedErrorProc: TISSigVerifySignatureVerificationFailedErrorProc): Boolean; overload;
|
|
function ISSigVerifySignature(const AFilename: String; const AAllowedKeys: array of TECDSAKey;
|
|
out AExpectedFileSize: Int64; out AExpectedFileHash: TSHA256Digest;
|
|
const AFileMissingErrorProc: TISSigVerifySignatureFileMissingErrorProc;
|
|
const ASigFileMissingErrorProc: TISSigVerifySignatureSigFileMissingErrorProc;
|
|
const AVerificationFailedErrorProc: TISSigVerifySignatureVerificationFailedErrorProc): Boolean; overload;
|
|
|
|
procedure ISSigExportPrivateKeyText(const AKey: TECDSAKey;
|
|
var APrivateKeyText: String);
|
|
procedure ISSigExportPublicKeyText(const AKey: TECDSAKey;
|
|
var APublicKeyText: String);
|
|
procedure ISSigConvertPublicKeyToStrings(const APublicKey: TECDSAPublicKey;
|
|
out APublicX, APublicY: String);
|
|
function ISSigParsePrivateKeyText(const AText: String;
|
|
out APrivateKey: TECDSAPrivateKey): TISSigImportKeyResult;
|
|
function ISSigParsePublicKeyText(const AText: String;
|
|
out APublicKey: TECDSAPublicKey): TISSigImportKeyResult;
|
|
function ISSigImportKeyText(const AKey: TECDSAKey; const AText: String;
|
|
const ANeedPrivateKey: Boolean): TISSigImportKeyResult;
|
|
function ISSigImportPublicKey(const AKey: TECDSAKey;
|
|
const AKeyID, APublicX, APublicY: String): TISSigImportKeyResult;
|
|
|
|
procedure ISSigCheckValidKeyID(const AKeyID: String);
|
|
procedure ISSigCheckValidPublicXOrY(const APublicXOrY: String);
|
|
function ISSigIsValidKeyIDForPublicXY(const AKeyID, APublicX, APublicY: String): Boolean;
|
|
|
|
function ISSigCalcStreamHash(const AStream: TStream): TSHA256Digest;
|
|
|
|
var
|
|
ISSigExt: String = '.issig';
|
|
|
|
implementation
|
|
|
|
uses
|
|
StringScanner;
|
|
|
|
const
|
|
ISSigTextFileLengthLimit = 500;
|
|
|
|
NonControlASCIICharsSet = [#32..#126];
|
|
DigitsSet = ['0'..'9'];
|
|
HexDigitsSet = DigitsSet + ['a'..'f'];
|
|
|
|
function ECDSAInt256ToString(const Value: TECDSAInt256): String;
|
|
begin
|
|
Result := SHA256DigestToString(TSHA256Digest(Value));
|
|
end;
|
|
|
|
function ECDSAInt256FromString(const S: String): TECDSAInt256;
|
|
begin
|
|
TSHA256Digest(Result) := SHA256DigestFromString(S);
|
|
end;
|
|
|
|
function CalcHashToSign(const AFileSize: Int64;
|
|
const AFileHash: TSHA256Digest): TSHA256Digest;
|
|
begin
|
|
var Context: TSHA256Context;
|
|
SHA256Init(Context);
|
|
SHA256Update(Context, AFileSize, SizeOf(AFileSize));
|
|
SHA256Update(Context, AFileHash, SizeOf(AFileHash));
|
|
Result := SHA256Final(Context);
|
|
end;
|
|
|
|
function CalcKeyID(const APublicKey: TECDSAPublicKey): TSHA256Digest;
|
|
begin
|
|
Result := SHA256Buf(APublicKey, SizeOf(APublicKey));
|
|
end;
|
|
|
|
function ConsumeLineValue(var SS: TStringScanner; const AIdent: String;
|
|
var AValue: String; const AMinValueLength, AMaxValueLength: Integer;
|
|
const AAllowedChars: TSysCharSet): Boolean;
|
|
begin
|
|
Result := False;
|
|
if SS.Consume(AIdent) and SS.Consume(' ') then
|
|
if SS.ConsumeMultiToString(AAllowedChars, AValue, AMinValueLength,
|
|
AMaxValueLength) > 0 then begin
|
|
{ CRLF and LF line breaks are allowed (but not CR) }
|
|
SS.Consume(#13);
|
|
Result := SS.Consume(#10);
|
|
end;
|
|
end;
|
|
|
|
function ISSigLoadTextFromFile(const AFilename: String): String;
|
|
{ Reads the specified file's contents into a string. This is intended only for
|
|
loading .issig and key files. If the file appears to be invalid (e.g., if
|
|
it is too large or contains invalid characters), then an empty string is
|
|
returned, which will be reported as malformed when it is processed by
|
|
ISSigVerifySignatureText or ISSigImportKeyText. }
|
|
begin
|
|
var U: UTF8String;
|
|
SetLength(U, ISSigTextFileLengthLimit + 1);
|
|
|
|
const F = TFileStream.Create(AFilename, fmOpenRead or fmShareDenyWrite);
|
|
try
|
|
const BytesRead = F.Read(U[Low(U)], Length(U));
|
|
if BytesRead >= Length(U) then
|
|
Exit('');
|
|
SetLength(U, BytesRead);
|
|
finally
|
|
F.Free;
|
|
end;
|
|
|
|
{ Defense-in-depth: Reject any non-CRLF control characters up front, as well
|
|
as any non-ASCII characters (to avoid any possible issues with converting
|
|
invalid multibyte characters) }
|
|
for var C in U do
|
|
if not CharInSet(C, [#10, #13, #32..#126]) then
|
|
Exit('');
|
|
|
|
Result := String(U);
|
|
end;
|
|
|
|
procedure ISSigSaveTextToFile(const AFilename, AText: String);
|
|
begin
|
|
const F = TFileStream.Create(AFilename, fmCreate or fmShareExclusive);
|
|
try
|
|
const U = UTF8String(AText);
|
|
if U <> '' then
|
|
F.WriteBuffer(U[Low(U)], Length(U));
|
|
finally
|
|
F.Free;
|
|
end;
|
|
end;
|
|
|
|
function ISSigCreateSignatureText(const AKey: TECDSAKey;
|
|
const AFileSize: Int64; const AFileHash: TSHA256Digest): String;
|
|
begin
|
|
{ File size is limited to 16 digits (enough for >9 EB) }
|
|
if (AFileSize < 0) or (AFileSize > {$IF CompilerVersion >= 36.0} 9_999_999_999_999_999 {$ELSE} 9999999999999999 {$ENDIF} ) then
|
|
raise Exception.Create('File size out of range');
|
|
|
|
var PublicKey: TECDSAPublicKey;
|
|
AKey.ExportPublicKey(PublicKey);
|
|
|
|
const HashToSign = CalcHashToSign(AFileSize, AFileHash);
|
|
var Sig: TECDSASignature;
|
|
AKey.SignHash(HashToSign, Sig);
|
|
|
|
Result := Format(
|
|
'format issig-v1'#13#10 +
|
|
'file-size %d'#13#10 +
|
|
'file-hash %s'#13#10 +
|
|
'key-id %s'#13#10 +
|
|
'sig-r %s'#13#10 +
|
|
'sig-s %s'#13#10,
|
|
[AFileSize,
|
|
SHA256DigestToString(AFileHash),
|
|
SHA256DigestToString(CalcKeyID(PublicKey)),
|
|
ECDSAInt256ToString(Sig.Sig_r),
|
|
ECDSAInt256ToString(Sig.Sig_s)]);
|
|
end;
|
|
|
|
function ISSigVerifySignatureText(const AAllowedKeys: array of TECDSAKey;
|
|
const AText: String; out AFileSize: Int64;
|
|
out AFileHash: TSHA256Digest; out AKeyUsedID: String): TISSigVerifySignatureResult;
|
|
var
|
|
TextValues: record
|
|
Format, FileSize, FileHash, KeyID, Sig_r, Sig_s: String;
|
|
end;
|
|
begin
|
|
{ To be extra safe, clear the "out" parameters just in case the caller isn't
|
|
properly checking the function result }
|
|
AFileSize := -1;
|
|
FillChar(AFileHash, SizeOf(AFileHash), 0);
|
|
AKeyUsedID := '';
|
|
|
|
if Length(AText) > ISSigTextFileLengthLimit then
|
|
Exit(vsrMalformed)
|
|
else if Length(AAllowedKeys) = 0 then
|
|
Exit(vsrKeyNotFound);
|
|
|
|
var SS := TStringScanner.Create(AText);
|
|
if not ConsumeLineValue(SS, 'format', TextValues.Format, 8, 8, NonControlASCIICharsSet) or
|
|
(TextValues.Format <> 'issig-v1') or
|
|
not ConsumeLineValue(SS, 'file-size', TextValues.FileSize, 1, 16, DigitsSet) or
|
|
not ConsumeLineValue(SS, 'file-hash', TextValues.FileHash, 64, 64, HexDigitsSet) or
|
|
not ConsumeLineValue(SS, 'key-id', TextValues.KeyID, 64, 64, HexDigitsSet) or
|
|
not ConsumeLineValue(SS, 'sig-r', TextValues.Sig_r, 64, 64, HexDigitsSet) or
|
|
not ConsumeLineValue(SS, 'sig-s', TextValues.Sig_s, 64, 64, HexDigitsSet) or
|
|
not SS.ReachedEnd then
|
|
Exit(vsrMalformed);
|
|
|
|
{ Don't allow leading zeros on file-size }
|
|
if (Length(TextValues.FileSize) > 1) and
|
|
(TextValues.FileSize[Low(TextValues.FileSize)] = '0') then
|
|
Exit(vsrMalformed);
|
|
|
|
{ Find the key that matches the key ID }
|
|
var KeyUsed: TECDSAKey := nil;
|
|
const KeyID = SHA256DigestFromString(TextValues.KeyID);
|
|
for var K in AAllowedKeys do begin
|
|
var PublicKey: TECDSAPublicKey;
|
|
K.ExportPublicKey(PublicKey);
|
|
if SHA256DigestsEqual(KeyID, CalcKeyID(PublicKey)) then begin
|
|
KeyUsed := K;
|
|
Break;
|
|
end;
|
|
end;
|
|
if KeyUsed = nil then
|
|
Exit(vsrKeyNotFound);
|
|
AKeyUsedID := TextValues.KeyID;
|
|
|
|
const UnverifiedFileSize = StrToInt64(TextValues.FileSize);
|
|
const UnverifiedFileHash = SHA256DigestFromString(TextValues.FileHash);
|
|
const HashToSign = CalcHashToSign(UnverifiedFileSize, UnverifiedFileHash);
|
|
var Sig: TECDSASignature;
|
|
Sig.Sig_r := ECDSAInt256FromString(TextValues.Sig_r);
|
|
Sig.Sig_s := ECDSAInt256FromString(TextValues.Sig_s);
|
|
if KeyUsed.VerifySignature(HashToSign, Sig) then begin
|
|
AFileSize := UnverifiedFileSize;
|
|
AFileHash := UnverifiedFileHash;
|
|
Result := vsrSuccess;
|
|
end else
|
|
Result := vsrBad;
|
|
end;
|
|
|
|
function ISSigVerifySignatureText(const AAllowedKeys: array of TECDSAKey;
|
|
const AText: String; out AFileSize: Int64;
|
|
out AFileHash: TSHA256Digest): TISSigVerifySignatureResult;
|
|
begin
|
|
var KeyUsedID: String;
|
|
Result := ISSigVerifySignatureText(AAllowedKeys, AText, AFileSize, AFileHash, KeyUsedID);
|
|
end;
|
|
|
|
function ISSigVerifySignature(const AFilename: String; const AAllowedKeys: array of TECDSAKey;
|
|
out AExpectedFileSize: Int64; out AExpectedFileHash: TSHA256Digest; out AKeyUsedID: String;
|
|
const AFileMissingErrorProc: TISSigVerifySignatureFileMissingErrorProc;
|
|
const ASigFileMissingErrorProc: TISSigVerifySignatureSigFileMissingErrorProc;
|
|
const AVerificationFailedErrorProc: TISSigVerifySignatureVerificationFailedErrorProc): Boolean;
|
|
|
|
function NewFileExists(const Name: String): Boolean;
|
|
{ Returns True if the specified file exists.
|
|
This function is better than Delphi's FileExists function because it works
|
|
on files in directories that don't have "list" permission. There is, however,
|
|
one other difference: FileExists allows wildcards, but this function does
|
|
not. }
|
|
begin
|
|
var Attr := GetFileAttributes(PChar(Name));
|
|
Result := (Attr <> INVALID_FILE_ATTRIBUTES) and (Attr and faDirectory = 0);
|
|
end;
|
|
|
|
begin
|
|
if Assigned(AFileMissingErrorProc) and not NewFileExists(AFilename) then begin
|
|
AFileMissingErrorProc(AFilename);
|
|
Exit(False);
|
|
end;
|
|
const SigFilename = AFilename + ISSigExt;
|
|
if not NewFileExists(SigFilename) then begin
|
|
if Assigned(ASigFileMissingErrorProc) then
|
|
ASigFileMissingErrorProc(AFilename, SigFilename);
|
|
Exit(False);
|
|
end;
|
|
const SigText = ISSigLoadTextFromFile(SigFilename);
|
|
const VerifyResult = ISSigVerifySignatureText(AAllowedKeys, SigText,
|
|
AExpectedFileSize, AExpectedFileHash, AKeyUsedID);
|
|
Result := VerifyResult = vsrSuccess;
|
|
if not Result and Assigned(AVerificationFailedErrorProc) then
|
|
AVerificationFailedErrorProc(AFilename, SigFilename, VerifyResult);
|
|
end;
|
|
|
|
function ISSigVerifySignature(const AFilename: String; const AAllowedKeys: array of TECDSAKey;
|
|
out AExpectedFileSize: Int64; out AExpectedFileHash: TSHA256Digest;
|
|
const AFileMissingErrorProc: TISSigVerifySignatureFileMissingErrorProc;
|
|
const ASigFileMissingErrorProc: TISSigVerifySignatureSigFileMissingErrorProc;
|
|
const AVerificationFailedErrorProc: TISSigVerifySignatureVerificationFailedErrorProc): Boolean;
|
|
begin
|
|
var KeyUsedID: String;
|
|
Result := ISSigVerifySignature(AFilename, AAllowedKeys, AExpectedFileSize,
|
|
AExpectedFileHash, KeyUsedID, AFileMissingErrorProc, ASigFileMissingErrorProc,
|
|
AVerificationFailedErrorProc);
|
|
end;
|
|
|
|
procedure ISSigExportPrivateKeyText(const AKey: TECDSAKey;
|
|
var APrivateKeyText: String);
|
|
begin
|
|
var PrivateKey: TECDSAPrivateKey;
|
|
try
|
|
AKey.ExportPrivateKey(PrivateKey);
|
|
|
|
APrivateKeyText := Format(
|
|
'format issig-private-key'#13#10 +
|
|
'key-id %s'#13#10 +
|
|
'public-x %s'#13#10 +
|
|
'public-y %s'#13#10 +
|
|
'private-d %s'#13#10,
|
|
[SHA256DigestToString(CalcKeyID(PrivateKey.PublicKey)),
|
|
ECDSAInt256ToString(PrivateKey.PublicKey.Public_x),
|
|
ECDSAInt256ToString(PrivateKey.PublicKey.Public_y),
|
|
ECDSAInt256ToString(PrivateKey.Private_d)]);
|
|
finally
|
|
PrivateKey.Clear;
|
|
end;
|
|
end;
|
|
|
|
procedure ISSigExportPublicKeyText(const AKey: TECDSAKey;
|
|
var APublicKeyText: String);
|
|
begin
|
|
var PublicKey: TECDSAPublicKey;
|
|
try
|
|
AKey.ExportPublicKey(PublicKey);
|
|
|
|
APublicKeyText := Format(
|
|
'format issig-public-key'#13#10 +
|
|
'key-id %s'#13#10 +
|
|
'public-x %s'#13#10 +
|
|
'public-y %s'#13#10,
|
|
[SHA256DigestToString(CalcKeyID(PublicKey)),
|
|
ECDSAInt256ToString(PublicKey.Public_x),
|
|
ECDSAInt256ToString(PublicKey.Public_y)]);
|
|
finally
|
|
PublicKey.Clear;
|
|
end;
|
|
end;
|
|
|
|
procedure ISSigConvertPublicKeyToStrings(const APublicKey: TECDSAPublicKey;
|
|
out APublicX, APublicY: String);
|
|
begin
|
|
APublicX := ECDSAInt256ToString(APublicKey.Public_x);
|
|
APublicY := ECDSAInt256ToString(APublicKey.Public_y);
|
|
end;
|
|
|
|
function InternalParseKeyText(const AText: String;
|
|
out APrivateKey: TECDSAPrivateKey;
|
|
const ANeedPrivateKey: Boolean): TISSigImportKeyResult;
|
|
var
|
|
TextValues: record
|
|
Format, KeyID, Public_x, Public_y, Private_d: String;
|
|
end;
|
|
begin
|
|
Result := ikrMalformed;
|
|
if Length(AText) > ISSigTextFileLengthLimit then
|
|
Exit;
|
|
|
|
var SS := TStringScanner.Create(AText);
|
|
if not ConsumeLineValue(SS, 'format', TextValues.Format, 16, 17, NonControlASCIICharsSet) then
|
|
Exit;
|
|
var HasPrivateKey := False;
|
|
if TextValues.Format = 'issig-private-key' then
|
|
HasPrivateKey := True
|
|
else if TextValues.Format = 'issig-public-key' then
|
|
{ already False }
|
|
else
|
|
Exit;
|
|
|
|
if not ConsumeLineValue(SS, 'key-id', TextValues.KeyID, 64, 64, HexDigitsSet) or
|
|
not ConsumeLineValue(SS, 'public-x', TextValues.Public_x, 64, 64, HexDigitsSet) or
|
|
not ConsumeLineValue(SS, 'public-y', TextValues.Public_y, 64, 64, HexDigitsSet) then
|
|
Exit;
|
|
if HasPrivateKey then
|
|
if not ConsumeLineValue(SS, 'private-d', TextValues.Private_d, 64, 64, HexDigitsSet) then
|
|
Exit;
|
|
if not SS.ReachedEnd then
|
|
Exit;
|
|
|
|
APrivateKey.Clear; { just because Private_d isn't always set }
|
|
APrivateKey.PublicKey.Public_x := ECDSAInt256FromString(TextValues.Public_x);
|
|
APrivateKey.PublicKey.Public_y := ECDSAInt256FromString(TextValues.Public_y);
|
|
|
|
{ Verify that the key ID is correct for the public key values }
|
|
if not SHA256DigestsEqual(SHA256DigestFromString(TextValues.KeyID),
|
|
CalcKeyID(APrivateKey.PublicKey)) then
|
|
Exit;
|
|
|
|
if ANeedPrivateKey then begin
|
|
if not HasPrivateKey then
|
|
Exit(ikrNotPrivateKey);
|
|
APrivateKey.Private_d := ECDSAInt256FromString(TextValues.Private_d);
|
|
end;
|
|
Result := ikrSuccess;
|
|
end;
|
|
|
|
function ISSigParsePrivateKeyText(const AText: String;
|
|
out APrivateKey: TECDSAPrivateKey): TISSigImportKeyResult;
|
|
begin
|
|
Result := InternalParseKeyText(AText, APrivateKey, True);
|
|
end;
|
|
|
|
function ISSigParsePublicKeyText(const AText: String;
|
|
out APublicKey: TECDSAPublicKey): TISSigImportKeyResult;
|
|
begin
|
|
var PrivateKey: TECDSAPrivateKey; { only PublicKey part is used }
|
|
Result := InternalParseKeyText(AText, PrivateKey, False);
|
|
if Result = ikrSuccess then
|
|
APublicKey := PrivateKey.PublicKey;
|
|
end;
|
|
|
|
function ISSigImportKeyText(const AKey: TECDSAKey; const AText: String;
|
|
const ANeedPrivateKey: Boolean): TISSigImportKeyResult;
|
|
begin
|
|
var PrivateKey: TECDSAPrivateKey;
|
|
try
|
|
Result := InternalParseKeyText(AText, PrivateKey, ANeedPrivateKey);
|
|
if Result = ikrSuccess then begin
|
|
if ANeedPrivateKey then
|
|
AKey.ImportPrivateKey(PrivateKey)
|
|
else
|
|
AKey.ImportPublicKey(PrivateKey.PublicKey);
|
|
end;
|
|
finally
|
|
PrivateKey.Clear;
|
|
end;
|
|
end;
|
|
|
|
function ISSigImportPublicKey(const AKey: TECDSAKey;
|
|
const AKeyID, APublicX, APublicY: String): TISSigImportKeyResult;
|
|
begin
|
|
var Publickey: TECDSAPublickey;
|
|
PublicKey.Public_x := ECDSAInt256FromString(APublicX);
|
|
PublicKey.Public_y := ECDSAInt256FromString(APublicY);
|
|
|
|
if AKeyID <> '' then begin
|
|
{ Verify that the key ID is correct for the public key values }
|
|
if not SHA256DigestsEqual(SHA256DigestFromString(AKeyID),
|
|
CalcKeyID(PublicKey)) then
|
|
Exit(ikrMalformed);
|
|
end;
|
|
|
|
AKey.ImportPublicKey(PublicKey);
|
|
Result := ikrSuccess;
|
|
end;
|
|
|
|
procedure ISSigCheckValidKeyID(const AKeyID: String);
|
|
begin
|
|
SHA256DigestFromString(AKeyID);
|
|
end;
|
|
|
|
procedure ISSigCheckValidPublicXOrY(const APublicXOrY: String);
|
|
begin
|
|
ECDSAInt256FromString(APublicXOrY);
|
|
end;
|
|
|
|
function ISSigIsValidKeyIDForPublicXY(const AKeyID, APublicX, APublicY: String): Boolean;
|
|
begin
|
|
var PublicKey: TECDSAPublicKey;
|
|
PublicKey.Public_x := ECDSAInt256FromString(APublicX);
|
|
PublicKey.Public_y := ECDSAInt256FromString(APublicY);
|
|
|
|
Result := SHA256DigestsEqual(SHA256DigestFromString(AKeyID),
|
|
CalcKeyID(PublicKey));
|
|
end;
|
|
|
|
function ISSigCalcStreamHash(const AStream: TStream): TSHA256Digest;
|
|
var
|
|
Buf: array[0..$FFFF] of Byte;
|
|
begin
|
|
var Context: TSHA256Context;
|
|
SHA256Init(Context);
|
|
while True do begin
|
|
const BytesRead = Cardinal(AStream.Read(Buf, SizeOf(Buf)));
|
|
if BytesRead = 0 then
|
|
Break;
|
|
SHA256Update(Context, Buf, BytesRead);
|
|
end;
|
|
Result := SHA256Final(Context);
|
|
end;
|
|
|
|
end.
|