Shared.LicenseFunc.pas 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. unit Shared.LicenseFunc;
  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. License functions used by both IDE and ISCC units
  8. }
  9. interface
  10. type
  11. TLicenseType = (ltSingle, ltTeam, ltEnterprise);
  12. TLicense = record
  13. Key, Name: String;
  14. Typ: TLicenseType;
  15. ExpirationDate: TDateTime;
  16. end;
  17. { "Expired" means update entitlement ended }
  18. TLicenseState = (lsNotLicensed, lsLicensed, lsExpiring, lsExpired, lsExpiredButUpdated);
  19. procedure ReadLicense;
  20. procedure WriteLicense;
  21. procedure RemoveLicense;
  22. function ParseLicenseKey(const LicenseKey: String; out License: TLicense): Boolean;
  23. function UpdateLicense(const LicenseKey: String): Boolean; overload;
  24. procedure UpdateLicense(const ALicense: TLicense); overload;
  25. function IsLicensed: Boolean;
  26. function GetLicenseKey: String;
  27. function GetChunkedLicenseKey: String;
  28. function GetLicenseState: TLicenseState;
  29. function GetLicenseeName: String;
  30. function GetLicenseeDescription: String;
  31. function GetLicenseTypeDescription: String;
  32. function GetLicenseDescription(const Prefix, Separator: String): String;
  33. implementation
  34. uses
  35. Windows,
  36. SysUtils, Classes, DateUtils, NetEncoding, RegularExpressions,
  37. ECDSA, SHA256, UnsignedFunc, Shared.ConfigIniFile;
  38. var
  39. License: TLicense;
  40. procedure ReadLicense;
  41. begin
  42. const Ini = TConfigIniFile.Create;
  43. try
  44. UpdateLicense(Ini.ReadString('License', 'LicenseKey', ''));
  45. finally
  46. Ini.Free;
  47. end;
  48. end;
  49. procedure WriteLicense;
  50. begin
  51. const Ini = TConfigIniFile.Create;
  52. try
  53. Ini.WriteString('License', 'LicenseKey', License.Key);
  54. finally
  55. Ini.Free;
  56. end;
  57. end;
  58. procedure RemoveLicense;
  59. begin
  60. const Ini = TConfigIniFile.Create;
  61. try
  62. Ini.DeleteKey('License', 'LicenseKey');
  63. finally
  64. Ini.Free;
  65. end;
  66. UpdateLicense('');
  67. end;
  68. function ParseLicenseKey(const LicenseKey: String; out License: TLicense): Boolean;
  69. function ECDSAInt256FromString(const S: String): TECDSAInt256;
  70. begin
  71. TSHA256Digest(Result) := SHA256DigestFromString(S);
  72. end;
  73. function TryDateFromDBDate(const S: string; out D: TDate): Boolean;
  74. begin
  75. const N = S.Length;
  76. if N = 8 then begin
  77. const Year = Word.Parse(Copy(S, 1, 4));
  78. const Month = Word.Parse(Copy(S, 5, 2));
  79. const Day = Word.Parse(Copy(S, 7, 2));
  80. D := EncodeDate(Year, Month, Day);
  81. Result := True;
  82. end else if N = 0 then begin
  83. D := 0;
  84. Result := True;
  85. end else
  86. Result := False;
  87. end;
  88. begin
  89. Result := False;
  90. const CleanLicenseKey = TRegEx.Replace(LicenseKey, '\s+', '');
  91. const N = Length(CleanLicenseKey);
  92. if N > 92 then begin { 92 = (64/3*4 rounded to a multiple of 4) + 4 }
  93. if (Copy(CleanLicenseKey, 1, 2) = 'in') and (Copy(CleanLicenseKey, N-1, 2) = 'no') then begin
  94. var EncodedKey := Copy(CleanLicenseKey, 3, N-4); { Strip 'in' and 'no' }
  95. EncodedKey := EncodedKey + StringOfChar('=', (4 - Length(EncodedKey) mod 4) mod 4); { Restore base64 padding }
  96. const DecodedKey = TNetEncoding.Base64.DecodeStringToBytes(EncodedKey);
  97. if Length(DecodedKey) > 64 then begin
  98. var Signature := Default(TECDSASignature);
  99. Move(DecodedKey[0], Signature.Sig_r[0], 32);
  100. Move(DecodedKey[32], Signature.Sig_s[0], 32);
  101. const LicenseBytes = Copy(DecodedKey, 64, MaxInt);
  102. const LicenseHash = SHA256Buf(LicenseBytes[0], ULength(LicenseBytes));
  103. var PublicKey: TECDSAPublickey;
  104. PublicKey.Public_x := ECDSAInt256FromString('76873a71a4d5cae3dfdb52f7e434582c25151e56338d6d7fd5423d1216dc3274');
  105. PublicKey.Public_y := ECDSAInt256FromString('4459f8d7c0e6c03e34806a4a4b949e0c16387fb8ff2f71d2d62ce6a29c713018');
  106. const ECDSAKey = TECDSAKey.Create;
  107. var Verified: Boolean;
  108. try
  109. ECDSAKey.ImportPublicKey(PublicKey);
  110. Verified := ECDSAKey.VerifySignature(LicenseHash, Signature);
  111. finally
  112. ECDSAKey.Free;
  113. end;
  114. if Verified then begin
  115. const LicenseString = TEncoding.UTF8.GetString(LicenseBytes);
  116. if LicenseString.StartsWith('1'#9) then begin
  117. const LicenseData = LicenseString.Split([#9]);
  118. if Length(LicenseData) = 4 then begin
  119. const LicenseeName = LicenseData[1];
  120. const LicenseType = LicenseData[2].ToInteger;
  121. var ExpirationDate: TDate;
  122. if TryDateFromDBDate(LicenseData[3], ExpirationDate) and (LicenseeName <> '') and
  123. (LicenseType >= Ord(Low(TLicenseType))) and (LicenseType <= Ord(High(TLicenseType))) then begin
  124. License.Key := CleanLicenseKey;
  125. License.Name := LicenseeName;
  126. License.Typ := TLicenseType(LicenseType);
  127. License.ExpirationDate := ExpirationDate;
  128. Result := True;
  129. end;
  130. end;
  131. end;
  132. end;
  133. end;
  134. end;
  135. end;
  136. end;
  137. function UpdateLicense(const LicenseKey: String): Boolean;
  138. begin
  139. if LicenseKey <> '' then
  140. Result := ParseLicenseKey(LicenseKey, License)
  141. else begin
  142. License := Default(TLicense);
  143. Result := True;
  144. end;
  145. end;
  146. procedure UpdateLicense(const ALicense: TLicense);
  147. begin
  148. License := ALicense;
  149. end;
  150. function IsLicensed: Boolean;
  151. begin
  152. Result := GetLicenseKey <> '';
  153. end;
  154. function GetLicenseKey: String;
  155. begin
  156. Result := License.Key;
  157. end;
  158. function GetChunkedLicenseKey: String;
  159. begin
  160. const Output = TStringList.Create;
  161. try
  162. var StartIndex := 1;
  163. const ChunkSize = 28;
  164. while StartIndex <= Length(License.Key) do begin
  165. Output.Add(Copy(License.Key, StartIndex, ChunkSize));
  166. StartIndex := StartIndex + ChunkSize;
  167. end;
  168. Result := Output.Text.Trim;
  169. finally
  170. Output.Free;
  171. end;
  172. end;
  173. var
  174. LinkerTimeStamp: TDateTime;
  175. ReadLinkerTimeStamp: Boolean;
  176. function GetLicenseState: TLicenseState;
  177. begin
  178. if not IsLicensed then
  179. Result := lsNotLicensed
  180. else if License.ExpirationDate <> 0 then begin
  181. if not ReadLinkerTimeStamp then begin
  182. try
  183. LinkerTimeStamp := PImageNtHeaders(HInstance + Cardinal(PImageDosHeader(HInstance)._lfanew)).FileHeader.TimeDateStamp / SecsPerDay + UnixDateDelta;
  184. except
  185. end;
  186. ReadLinkerTimeStamp := True;
  187. end;
  188. if (LinkerTimeStamp <> 0) and (LinkerTimeStamp > License.ExpirationDate) then
  189. Result := lsExpiredButUpdated
  190. else begin
  191. const CurrentDate = Date;
  192. if License.ExpirationDate < CurrentDate then
  193. Result := lsExpired
  194. else if License.ExpirationDate < IncMonth(CurrentDate, 1) then
  195. Result := lsExpiring
  196. else
  197. Result := lsLicensed;
  198. end;
  199. end else
  200. Result := lsLicensed;
  201. end;
  202. function GetLicenseeName: String;
  203. begin
  204. Result := License.Name;
  205. end;
  206. function GetLicenseeDescription: String;
  207. begin
  208. const LicenseState = GetLicenseState;
  209. if LicenseState <> lsNotLicensed then begin
  210. Result := GetLicenseeName;
  211. if LicenseState = lsExpired then
  212. Result := Result + ' (Update entitlement ended)'
  213. else if LicenseState = lsExpiredButUpdated then
  214. Result := Result + ' (Update entitlement ended but updated anyway)';
  215. end else
  216. Result := 'Non-commercial use only';
  217. end;
  218. function GetLicenseTypeDescription: String;
  219. begin
  220. case License.Typ of
  221. ltSingle: Result := 'Single User';
  222. ltTeam: Result := 'Team';
  223. ltEnterprise: Result := 'Enterprise';
  224. else
  225. raise Exception.Create('Unknown License.Typ');
  226. end;
  227. Result := 'Inno Setup ' + Result + ' License';
  228. end;
  229. function GetLicenseDescription(const Prefix, Separator: String): String;
  230. begin
  231. if IsLicensed then begin
  232. Result := Prefix + GetLicenseeName + ', ' + GetLicenseTypeDescription + '.' + Separator;
  233. if License.ExpirationDate <> 0 then
  234. Result := Result + 'Includes updates until ' + DateToStr(License.ExpirationDate) + ', major and minor.'
  235. else
  236. Result := Result + 'Includes all future updates, major and minor.';
  237. end else
  238. Result := GetLicenseeDescription + '.';
  239. end;
  240. end.