ISSigFunc.pas 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  1. unit ISSigFunc;
  2. {
  3. Inno Setup
  4. Copyright (C) 1997-2025 Jordan Russell
  5. Portions by Martijn Laan
  6. For conditions of distribution and use, see LICENSE.TXT.
  7. Functions for creating/verifying .issig signatures and importing/exporting
  8. text-based keys
  9. }
  10. interface
  11. uses
  12. Windows, SysUtils, Classes, ECDSA, SHA256;
  13. type
  14. TISSigVerifySignatureResult = (vsrSuccess, vsrMalformed, vsrKeyNotFound,
  15. vsrBad);
  16. TISSigImportKeyResult = (ikrSuccess, ikrMalformed, ikrNotPrivateKey);
  17. TISSigVerifySignatureFileMissingErrorProc = reference to procedure(const Filename: String);
  18. TISSigVerifySignatureSigFileMissingErrorProc = reference to procedure(const Filename, SigFilename: String);
  19. TISSigVerifySignatureVerificationFailedErrorProc = reference to procedure(const Filename, SigFilename: String; const VerifyResult: TISSigVerifySignatureResult);
  20. { Preferred, hardened functions for loading/saving .issig and key file text }
  21. function ISSigLoadTextFromFile(const AFilename: String): String;
  22. procedure ISSigSaveTextToFile(const AFilename, AText: String);
  23. function ISSigCreateSignatureText(const AKey: TECDSAKey;
  24. const AFileName: String; const AFileSize: Int64; const AFileHash: TSHA256Digest): String;
  25. function ISSigVerifySignatureText(const AAllowedKeys: array of TECDSAKey;
  26. const AText: String; out AFileName: String; out AFileSize: Int64;
  27. out AFileHash: TSHA256Digest): TISSigVerifySignatureResult; overload;
  28. function ISSigVerifySignatureText(const AAllowedKeys: array of TECDSAKey;
  29. const AText: String; out AFileName: String; out AFileSize: Int64;
  30. out AFileHash: TSHA256Digest; out AKeyUsedID: String): TISSigVerifySignatureResult; overload;
  31. function ISSigVerifySignature(const AFilename: String; const AAllowedKeys: array of TECDSAKey;
  32. out AExpectedFileName: String; out AExpectedFileSize: Int64; out AExpectedFileHash: TSHA256Digest;
  33. out AKeyUsedID: String;
  34. const AFileMissingErrorProc: TISSigVerifySignatureFileMissingErrorProc;
  35. const ASigFileMissingErrorProc: TISSigVerifySignatureSigFileMissingErrorProc;
  36. const AVerificationFailedErrorProc: TISSigVerifySignatureVerificationFailedErrorProc): Boolean; overload;
  37. function ISSigVerifySignature(const AFilename: String; const AAllowedKeys: array of TECDSAKey;
  38. out AExpectedFileName: String; out AExpectedFileSize: Int64; out AExpectedFileHash: TSHA256Digest;
  39. const AFileMissingErrorProc: TISSigVerifySignatureFileMissingErrorProc;
  40. const ASigFileMissingErrorProc: TISSigVerifySignatureSigFileMissingErrorProc;
  41. const AVerificationFailedErrorProc: TISSigVerifySignatureVerificationFailedErrorProc): Boolean; overload;
  42. procedure ISSigExportPrivateKeyText(const AKey: TECDSAKey;
  43. var APrivateKeyText: String);
  44. procedure ISSigExportPublicKeyText(const AKey: TECDSAKey;
  45. var APublicKeyText: String);
  46. procedure ISSigConvertPublicKeyToStrings(const APublicKey: TECDSAPublicKey;
  47. out APublicX, APublicY: String);
  48. function ISSigParsePrivateKeyText(const AText: String;
  49. out APrivateKey: TECDSAPrivateKey): TISSigImportKeyResult;
  50. function ISSigParsePublicKeyText(const AText: String;
  51. out APublicKey: TECDSAPublicKey): TISSigImportKeyResult;
  52. function ISSigImportKeyText(const AKey: TECDSAKey; const AText: String;
  53. const ANeedPrivateKey: Boolean): TISSigImportKeyResult;
  54. function ISSigImportPublicKey(const AKey: TECDSAKey;
  55. const AKeyID, APublicX, APublicY: String): TISSigImportKeyResult;
  56. procedure ISSigCheckValidKeyID(const AKeyID: String);
  57. procedure ISSigCheckValidPublicXOrY(const APublicXOrY: String);
  58. function ISSigIsValidKeyIDForPublicXY(const AKeyID, APublicX, APublicY: String): Boolean;
  59. function ISSigCalcStreamHash(const AStream: TStream): TSHA256Digest;
  60. var
  61. ISSigExt: String = '.issig';
  62. implementation
  63. uses
  64. StringScanner;
  65. const
  66. ISSigTextFileLengthLimit = 2500;
  67. NonControlASCIICharsSet = [#32..#126];
  68. UTF8HighCharsSet = [#128..#244];
  69. AllHighCharsSet = [#128..#255];
  70. DigitsSet = ['0'..'9'];
  71. HexDigitsSet = DigitsSet + ['a'..'f'];
  72. function ECDSAInt256ToString(const Value: TECDSAInt256): String;
  73. begin
  74. Result := SHA256DigestToString(TSHA256Digest(Value));
  75. end;
  76. function ECDSAInt256FromString(const S: String): TECDSAInt256;
  77. begin
  78. TSHA256Digest(Result) := SHA256DigestFromString(S);
  79. end;
  80. function CalcHashToSign(const AIncludeFileNameAndTag: Boolean; const AFileName: String; const AFileSize: Int64;
  81. const AFileHash: TSHA256Digest; const AFileTag: String): TSHA256Digest;
  82. procedure SHA256UpdateWithString(var Context: TSHA256Context; const S: String);
  83. begin
  84. const U = UTF8String(S);
  85. const N = UInt32(Length(U));
  86. SHA256Update(Context, N, SizeOf(N));
  87. if N > 0 then
  88. SHA256Update(Context, Pointer(U)^, N*SizeOf(U[1]));
  89. end;
  90. begin
  91. var Context: TSHA256Context;
  92. SHA256Init(Context);
  93. if AIncludeFileNameAndTag then begin
  94. SHA256UpdateWithString(Context, AFileName);
  95. SHA256UpdateWithString(Context, AFileTag);
  96. end;
  97. SHA256Update(Context, AFileSize, SizeOf(AFileSize));
  98. SHA256Update(Context, AFileHash, SizeOf(AFileHash));
  99. Result := SHA256Final(Context);
  100. end;
  101. function CalcKeyID(const APublicKey: TECDSAPublicKey): TSHA256Digest;
  102. begin
  103. Result := SHA256Buf(APublicKey, SizeOf(APublicKey));
  104. end;
  105. function ConsumeLineValue(var SS: TStringScanner; const AIdent: String;
  106. var AValue: String; const AMinValueLength, AMaxValueLength: Integer;
  107. const AAllowedChars: TSysCharSet; const AAllowAllCharsAboveFF: Boolean = False;
  108. const ARequireQuotes: Boolean = False): Boolean;
  109. begin
  110. Result := False;
  111. if SS.Consume(AIdent) and SS.Consume(' ') and (not ARequireQuotes or SS.Consume('"')) then
  112. if SS.ConsumeMultiToString(AAllowedChars, AValue, AAllowAllCharsAboveFF,
  113. AMinValueLength, AMaxValueLength) >= AMinValueLength then begin
  114. if not ARequireQuotes or SS.Consume('"') then begin
  115. { CRLF and LF line breaks are allowed (but not CR) }
  116. SS.Consume(#13);
  117. Result := SS.Consume(#10);
  118. end;
  119. end;
  120. end;
  121. function ISSigLoadTextFromFile(const AFilename: String): String;
  122. { Reads the specified file's contents into a string. This is intended only for
  123. loading .issig and key files. If the file appears to be invalid (e.g., if
  124. it is too large or contains invalid characters), then an empty string is
  125. returned, which will be reported as malformed when it is processed by
  126. ISSigVerifySignatureText or ISSigImportKeyText. }
  127. begin
  128. var U: UTF8String;
  129. SetLength(U, ISSigTextFileLengthLimit + 1);
  130. const F = TFileStream.Create(AFilename, fmOpenRead or fmShareDenyWrite);
  131. try
  132. const BytesRead = F.Read(U[Low(U)], Length(U));
  133. if BytesRead >= Length(U) then
  134. Exit('');
  135. SetLength(U, BytesRead);
  136. finally
  137. F.Free;
  138. end;
  139. { Defense-in-depth: Reject any non-CRLF control characters up front, as well
  140. as any byte values that are never used in UTF-8 encoding }
  141. for var C in U do
  142. if not CharInSet(C, [#10, #13] + NonControlASCIICharsSet + UTF8HighCharsSet) then
  143. Exit('');
  144. { Do round-trip check to catch invalid sequences }
  145. const UTF16Text = String(U);
  146. if UTF8String(UTF16Text) <> U then
  147. Exit('');
  148. Result := UTF16Text;
  149. end;
  150. procedure ISSigSaveTextToFile(const AFilename, AText: String);
  151. begin
  152. const F = TFileStream.Create(AFilename, fmCreate or fmShareExclusive);
  153. try
  154. const U = UTF8String(AText);
  155. if U <> '' then
  156. F.WriteBuffer(U[Low(U)], Length(U));
  157. finally
  158. F.Free;
  159. end;
  160. end;
  161. function ISSigCreateSignatureText(const AKey: TECDSAKey;
  162. const AFileName: String; const AFileSize: Int64; const AFileHash: TSHA256Digest): String;
  163. begin
  164. const AFileTag = ''; { Should be a parameter in the future }
  165. { Ensure unverifiable signature files can't be created accidentally }
  166. const UTF8FileName = UTF8String(AFileName);
  167. if Length(UTF8FileName) > 1000 then
  168. raise Exception.Create('File name is too long');
  169. if String(UTF8FileName) <> AFileName then
  170. raise Exception.Create('File name contains invalid surrogate pairs');
  171. for var C in UTF8FileName do
  172. if not (C in ((NonControlASCIICharsSet - ['"']) + UTF8HighCharsSet)) then { Note this rejects quotes }
  173. raise Exception.Create('File name contains invalid characters');
  174. { File size is limited to 16 digits (enough for >9 EB) }
  175. if (AFileSize < 0) or (AFileSize > {$IF CompilerVersion >= 36.0} 9_999_999_999_999_999 {$ELSE} 9999999999999999 {$ENDIF} ) then
  176. raise Exception.Create('File size out of range');
  177. var PublicKey: TECDSAPublicKey;
  178. AKey.ExportPublicKey(PublicKey);
  179. const HashToSign = CalcHashToSign(True, AFileName, AFileSize, AFileHash, AFileTag);
  180. var Sig: TECDSASignature;
  181. AKey.SignHash(HashToSign, Sig);
  182. Result := Format(
  183. 'format issig-v2'#13#10 +
  184. 'file-name "%s"'#13#10 +
  185. 'file-size %d'#13#10 +
  186. 'file-hash %s'#13#10 +
  187. 'file-tag "%s"'#13#10 +
  188. 'key-id %s'#13#10 +
  189. 'sig-r %s'#13#10 +
  190. 'sig-s %s'#13#10,
  191. [AFileName,
  192. AFileSize,
  193. SHA256DigestToString(AFileHash),
  194. AFileTag,
  195. SHA256DigestToString(CalcKeyID(PublicKey)),
  196. ECDSAInt256ToString(Sig.Sig_r),
  197. ECDSAInt256ToString(Sig.Sig_s)]);
  198. end;
  199. function ISSigVerifySignatureText(const AAllowedKeys: array of TECDSAKey;
  200. const AText: String; out AFileName: String; out AFileSize: Int64;
  201. out AFileHash: TSHA256Digest; out AKeyUsedID: String): TISSigVerifySignatureResult;
  202. function FormatToVersion(const Format: String; out Version: Integer): Boolean;
  203. begin
  204. if Format = 'issig-v1' then begin
  205. Version := 1;
  206. Exit(True);
  207. end else if Format = 'issig-v2' then begin
  208. Version := 2;
  209. Exit(True);
  210. end else
  211. Exit(False);
  212. end;
  213. var
  214. TextValues: record
  215. Format, FileName, FileSize, FileHash, FileTag, KeyID, Sig_r, Sig_s: String;
  216. Version: Integer;
  217. end;
  218. begin
  219. { To be extra safe, clear the "out" parameters just in case the caller isn't
  220. properly checking the function result }
  221. AFileName := '';
  222. AFileSize := -1;
  223. FillChar(AFileHash, SizeOf(AFileHash), 0);
  224. var AFileTag := ''; { Should be a parameter in the future }
  225. AKeyUsedID := '';
  226. if Length(AText) > ISSigTextFileLengthLimit then
  227. Exit(vsrMalformed)
  228. else if Length(AAllowedKeys) = 0 then
  229. Exit(vsrKeyNotFound);
  230. var SS := TStringScanner.Create(AText);
  231. if not ConsumeLineValue(SS, 'format', TextValues.Format, 8, 8, NonControlASCIICharsSet) or
  232. not FormatToVersion(TextValues.Format, TextValues.Version) or
  233. ((TextValues.Version >= 2) and not ConsumeLineValue(SS, 'file-name', TextValues.FileName, 0, MaxInt,
  234. (NonControlASCIICharsSet - ['"']) + AllHighCharsSet, True, True)) or
  235. not ConsumeLineValue(SS, 'file-size', TextValues.FileSize, 1, 16, DigitsSet) or
  236. not ConsumeLineValue(SS, 'file-hash', TextValues.FileHash, 64, 64, HexDigitsSet) or
  237. ((TextValues.Version >= 2) and not ConsumeLineValue(SS, 'file-tag', TextValues.FileTag, 0, MaxInt,
  238. (NonControlASCIICharsSet - ['"']) + AllHighCharsSet, True, True)) or
  239. not ConsumeLineValue(SS, 'key-id', TextValues.KeyID, 64, 64, HexDigitsSet) or
  240. not ConsumeLineValue(SS, 'sig-r', TextValues.Sig_r, 64, 64, HexDigitsSet) or
  241. not ConsumeLineValue(SS, 'sig-s', TextValues.Sig_s, 64, 64, HexDigitsSet) or
  242. not SS.ReachedEnd then
  243. Exit(vsrMalformed);
  244. { Don't allow leading zeros on file-size }
  245. if (Length(TextValues.FileSize) > 1) and
  246. (TextValues.FileSize[Low(TextValues.FileSize)] = '0') then
  247. Exit(vsrMalformed);
  248. { Find the key that matches the key ID }
  249. var KeyUsed: TECDSAKey := nil;
  250. const KeyID = SHA256DigestFromString(TextValues.KeyID);
  251. for var K in AAllowedKeys do begin
  252. var PublicKey: TECDSAPublicKey;
  253. K.ExportPublicKey(PublicKey);
  254. if SHA256DigestsEqual(KeyID, CalcKeyID(PublicKey)) then begin
  255. KeyUsed := K;
  256. Break;
  257. end;
  258. end;
  259. if KeyUsed = nil then
  260. Exit(vsrKeyNotFound);
  261. AKeyUsedID := TextValues.KeyID;
  262. const UnverifiedFileName = TextValues.FileName;
  263. const UnverifiedFileSize = StrToInt64(TextValues.FileSize);
  264. const UnverifiedFileHash = SHA256DigestFromString(TextValues.FileHash);
  265. const UnverifiedFileTag = TextValues.FileTag;
  266. const HashToSign = CalcHashToSign(TextValues.Version >= 2, UnverifiedFileName,
  267. UnverifiedFileSize, UnverifiedFileHash, UnverifiedFileTag);
  268. var Sig: TECDSASignature;
  269. Sig.Sig_r := ECDSAInt256FromString(TextValues.Sig_r);
  270. Sig.Sig_s := ECDSAInt256FromString(TextValues.Sig_s);
  271. if KeyUsed.VerifySignature(HashToSign, Sig) then begin
  272. AFileName := UnverifiedFileName;
  273. AFileSize := UnverifiedFileSize;
  274. AFileHash := UnverifiedFileHash;
  275. AFileTag := UnverifiedFileTag;
  276. Result := vsrSuccess;
  277. end else
  278. Result := vsrBad;
  279. end;
  280. function ISSigVerifySignatureText(const AAllowedKeys: array of TECDSAKey;
  281. const AText: String; out AFileName: String; out AFileSize: Int64;
  282. out AFileHash: TSHA256Digest): TISSigVerifySignatureResult;
  283. begin
  284. var KeyUsedID: String;
  285. Result := ISSigVerifySignatureText(AAllowedKeys, AText, AFileName, AFileSize, AFileHash, KeyUsedID);
  286. end;
  287. function ISSigVerifySignature(const AFilename: String; const AAllowedKeys: array of TECDSAKey;
  288. out AExpectedFileName: String; out AExpectedFileSize: Int64; out AExpectedFileHash: TSHA256Digest;
  289. out AKeyUsedID: String;
  290. const AFileMissingErrorProc: TISSigVerifySignatureFileMissingErrorProc;
  291. const ASigFileMissingErrorProc: TISSigVerifySignatureSigFileMissingErrorProc;
  292. const AVerificationFailedErrorProc: TISSigVerifySignatureVerificationFailedErrorProc): Boolean;
  293. function NewFileExists(const Name: String): Boolean;
  294. { Returns True if the specified file exists.
  295. This function is better than Delphi's FileExists function because it works
  296. on files in directories that don't have "list" permission. There is, however,
  297. one other difference: FileExists allows wildcards, but this function does
  298. not. }
  299. begin
  300. var Attr := GetFileAttributes(PChar(Name));
  301. Result := (Attr <> INVALID_FILE_ATTRIBUTES) and (Attr and faDirectory = 0);
  302. end;
  303. begin
  304. if Assigned(AFileMissingErrorProc) and not NewFileExists(AFilename) then begin
  305. AFileMissingErrorProc(AFilename);
  306. Exit(False);
  307. end;
  308. const SigFilename = AFilename + ISSigExt;
  309. if not NewFileExists(SigFilename) then begin
  310. if Assigned(ASigFileMissingErrorProc) then
  311. ASigFileMissingErrorProc(AFilename, SigFilename);
  312. Exit(False);
  313. end;
  314. const SigText = ISSigLoadTextFromFile(SigFilename);
  315. const VerifyResult = ISSigVerifySignatureText(AAllowedKeys, SigText,
  316. AExpectedFileName, AExpectedFileSize, AExpectedFileHash, AKeyUsedID);
  317. Result := VerifyResult = vsrSuccess;
  318. if not Result and Assigned(AVerificationFailedErrorProc) then
  319. AVerificationFailedErrorProc(AFilename, SigFilename, VerifyResult);
  320. end;
  321. function ISSigVerifySignature(const AFilename: String; const AAllowedKeys: array of TECDSAKey;
  322. out AExpectedFileName: String; out AExpectedFileSize: Int64; out AExpectedFileHash: TSHA256Digest;
  323. const AFileMissingErrorProc: TISSigVerifySignatureFileMissingErrorProc;
  324. const ASigFileMissingErrorProc: TISSigVerifySignatureSigFileMissingErrorProc;
  325. const AVerificationFailedErrorProc: TISSigVerifySignatureVerificationFailedErrorProc): Boolean;
  326. begin
  327. var KeyUsedID: String;
  328. Result := ISSigVerifySignature(AFilename, AAllowedKeys, AExpectedFileName, AExpectedFileSize,
  329. AExpectedFileHash, KeyUsedID, AFileMissingErrorProc, ASigFileMissingErrorProc,
  330. AVerificationFailedErrorProc);
  331. end;
  332. procedure ISSigExportPrivateKeyText(const AKey: TECDSAKey;
  333. var APrivateKeyText: String);
  334. begin
  335. var PrivateKey: TECDSAPrivateKey;
  336. try
  337. AKey.ExportPrivateKey(PrivateKey);
  338. APrivateKeyText := Format(
  339. 'format issig-private-key'#13#10 +
  340. 'key-id %s'#13#10 +
  341. 'public-x %s'#13#10 +
  342. 'public-y %s'#13#10 +
  343. 'private-d %s'#13#10,
  344. [SHA256DigestToString(CalcKeyID(PrivateKey.PublicKey)),
  345. ECDSAInt256ToString(PrivateKey.PublicKey.Public_x),
  346. ECDSAInt256ToString(PrivateKey.PublicKey.Public_y),
  347. ECDSAInt256ToString(PrivateKey.Private_d)]);
  348. finally
  349. PrivateKey.Clear;
  350. end;
  351. end;
  352. procedure ISSigExportPublicKeyText(const AKey: TECDSAKey;
  353. var APublicKeyText: String);
  354. begin
  355. var PublicKey: TECDSAPublicKey;
  356. try
  357. AKey.ExportPublicKey(PublicKey);
  358. APublicKeyText := Format(
  359. 'format issig-public-key'#13#10 +
  360. 'key-id %s'#13#10 +
  361. 'public-x %s'#13#10 +
  362. 'public-y %s'#13#10,
  363. [SHA256DigestToString(CalcKeyID(PublicKey)),
  364. ECDSAInt256ToString(PublicKey.Public_x),
  365. ECDSAInt256ToString(PublicKey.Public_y)]);
  366. finally
  367. PublicKey.Clear;
  368. end;
  369. end;
  370. procedure ISSigConvertPublicKeyToStrings(const APublicKey: TECDSAPublicKey;
  371. out APublicX, APublicY: String);
  372. begin
  373. APublicX := ECDSAInt256ToString(APublicKey.Public_x);
  374. APublicY := ECDSAInt256ToString(APublicKey.Public_y);
  375. end;
  376. function InternalParseKeyText(const AText: String;
  377. out APrivateKey: TECDSAPrivateKey;
  378. const ANeedPrivateKey: Boolean): TISSigImportKeyResult;
  379. var
  380. TextValues: record
  381. Format, KeyID, Public_x, Public_y, Private_d: String;
  382. end;
  383. begin
  384. Result := ikrMalformed;
  385. if Length(AText) > ISSigTextFileLengthLimit then
  386. Exit;
  387. var SS := TStringScanner.Create(AText);
  388. if not ConsumeLineValue(SS, 'format', TextValues.Format, 16, 17, NonControlASCIICharsSet) then
  389. Exit;
  390. var HasPrivateKey := False;
  391. if TextValues.Format = 'issig-private-key' then
  392. HasPrivateKey := True
  393. else if TextValues.Format = 'issig-public-key' then
  394. { already False }
  395. else
  396. Exit;
  397. if not ConsumeLineValue(SS, 'key-id', TextValues.KeyID, 64, 64, HexDigitsSet) or
  398. not ConsumeLineValue(SS, 'public-x', TextValues.Public_x, 64, 64, HexDigitsSet) or
  399. not ConsumeLineValue(SS, 'public-y', TextValues.Public_y, 64, 64, HexDigitsSet) then
  400. Exit;
  401. if HasPrivateKey then
  402. if not ConsumeLineValue(SS, 'private-d', TextValues.Private_d, 64, 64, HexDigitsSet) then
  403. Exit;
  404. if not SS.ReachedEnd then
  405. Exit;
  406. APrivateKey.Clear; { just because Private_d isn't always set }
  407. APrivateKey.PublicKey.Public_x := ECDSAInt256FromString(TextValues.Public_x);
  408. APrivateKey.PublicKey.Public_y := ECDSAInt256FromString(TextValues.Public_y);
  409. { Verify that the key ID is correct for the public key values }
  410. if not SHA256DigestsEqual(SHA256DigestFromString(TextValues.KeyID),
  411. CalcKeyID(APrivateKey.PublicKey)) then
  412. Exit;
  413. if ANeedPrivateKey then begin
  414. if not HasPrivateKey then
  415. Exit(ikrNotPrivateKey);
  416. APrivateKey.Private_d := ECDSAInt256FromString(TextValues.Private_d);
  417. end;
  418. Result := ikrSuccess;
  419. end;
  420. function ISSigParsePrivateKeyText(const AText: String;
  421. out APrivateKey: TECDSAPrivateKey): TISSigImportKeyResult;
  422. begin
  423. Result := InternalParseKeyText(AText, APrivateKey, True);
  424. end;
  425. function ISSigParsePublicKeyText(const AText: String;
  426. out APublicKey: TECDSAPublicKey): TISSigImportKeyResult;
  427. begin
  428. var PrivateKey: TECDSAPrivateKey; { only PublicKey part is used }
  429. Result := InternalParseKeyText(AText, PrivateKey, False);
  430. if Result = ikrSuccess then
  431. APublicKey := PrivateKey.PublicKey;
  432. end;
  433. function ISSigImportKeyText(const AKey: TECDSAKey; const AText: String;
  434. const ANeedPrivateKey: Boolean): TISSigImportKeyResult;
  435. begin
  436. var PrivateKey: TECDSAPrivateKey;
  437. try
  438. Result := InternalParseKeyText(AText, PrivateKey, ANeedPrivateKey);
  439. if Result = ikrSuccess then begin
  440. if ANeedPrivateKey then
  441. AKey.ImportPrivateKey(PrivateKey)
  442. else
  443. AKey.ImportPublicKey(PrivateKey.PublicKey);
  444. end;
  445. finally
  446. PrivateKey.Clear;
  447. end;
  448. end;
  449. function ISSigImportPublicKey(const AKey: TECDSAKey;
  450. const AKeyID, APublicX, APublicY: String): TISSigImportKeyResult;
  451. begin
  452. var Publickey: TECDSAPublickey;
  453. PublicKey.Public_x := ECDSAInt256FromString(APublicX);
  454. PublicKey.Public_y := ECDSAInt256FromString(APublicY);
  455. if AKeyID <> '' then begin
  456. { Verify that the key ID is correct for the public key values }
  457. if not SHA256DigestsEqual(SHA256DigestFromString(AKeyID),
  458. CalcKeyID(PublicKey)) then
  459. Exit(ikrMalformed);
  460. end;
  461. AKey.ImportPublicKey(PublicKey);
  462. Result := ikrSuccess;
  463. end;
  464. procedure ISSigCheckValidKeyID(const AKeyID: String);
  465. begin
  466. SHA256DigestFromString(AKeyID);
  467. end;
  468. procedure ISSigCheckValidPublicXOrY(const APublicXOrY: String);
  469. begin
  470. ECDSAInt256FromString(APublicXOrY);
  471. end;
  472. function ISSigIsValidKeyIDForPublicXY(const AKeyID, APublicX, APublicY: String): Boolean;
  473. begin
  474. var PublicKey: TECDSAPublicKey;
  475. PublicKey.Public_x := ECDSAInt256FromString(APublicX);
  476. PublicKey.Public_y := ECDSAInt256FromString(APublicY);
  477. Result := SHA256DigestsEqual(SHA256DigestFromString(AKeyID),
  478. CalcKeyID(PublicKey));
  479. end;
  480. function ISSigCalcStreamHash(const AStream: TStream): TSHA256Digest;
  481. var
  482. Buf: array[0..$FFFF] of Byte;
  483. begin
  484. var Context: TSHA256Context;
  485. SHA256Init(Context);
  486. while True do begin
  487. const BytesRead = Cardinal(AStream.Read(Buf, SizeOf(Buf)));
  488. if BytesRead = 0 then
  489. Break;
  490. SHA256Update(Context, Buf, BytesRead);
  491. end;
  492. Result := SHA256Final(Context);
  493. end;
  494. end.