tcjwt.pp 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706
  1. unit tcjwt;
  2. {$mode objfpc}{$H+}
  3. interface
  4. uses
  5. Classes, SysUtils, fpcunit, testregistry, DateUtils, fprsa, fphashutils,
  6. fpjwt, fpjwarsa;
  7. type
  8. { TMyClaims }
  9. TMyClaims = Class(TClaims)
  10. private
  11. FAdmin: Boolean;
  12. FName: string;
  13. Published
  14. Property Name : string Read FName Write FName;
  15. Property admin : Boolean Read FAdmin Write FAdmin;
  16. end;
  17. { TMyJWT }
  18. TMyJWT = Class(TJWT)
  19. Function CreateClaims : TClaims; override;
  20. end;
  21. { TTestJWT }
  22. TTestJWT= class(TTestCase)
  23. private
  24. FJWT: TJWT;
  25. FKey : TJWTKey;
  26. FVerifyResult : TJWT;
  27. protected
  28. procedure SetUp; override;
  29. procedure TearDown; override;
  30. Property JWT : TJWT Read FJWT;
  31. Property Key : TJWTKey Read FKey;
  32. procedure GetTestPEM(out aPrivateKeyPEM, aPublicKeyPEM: string);
  33. procedure TestVerifyRSAPem(SignerClass: TJWTSignerClass); virtual;
  34. published
  35. procedure TestSignNone;
  36. procedure TestVerifyNone;
  37. // SHA
  38. procedure TestSignSHA256;
  39. procedure TestVerifySHA256;
  40. procedure TestSignSHA512;
  41. procedure TestVerifySHA512;
  42. procedure TestSignSHA384;
  43. procedure TestVerifySHA384;
  44. // ES
  45. procedure TestSignES256;
  46. procedure TestVerifyES256;
  47. procedure TestVerifyES256Pem;
  48. // RSA
  49. procedure TestVerifyRS256Pem;
  50. procedure TestVerifyRS384Pem;
  51. procedure TestVerifyRS512Pem;
  52. procedure TestVerifyRS256_rfc7515;
  53. procedure TestI2OSP;
  54. procedure TestMGF1SHA1;
  55. procedure TestMGF1SHA256;
  56. procedure TestVerifyPS256;
  57. procedure TestVerifyPS256Pem;
  58. procedure TestVerifyPS384Pem;
  59. procedure TestVerifyPS512Pem;
  60. end;
  61. implementation
  62. uses
  63. basenenc, fpsha256, fpjwasha256, fpsha512, fpjwasha512, fpjwasha384, fpjwaes256, fpecc, fppem;
  64. { TMyJWT }
  65. function TMyJWT.CreateClaims: TClaims;
  66. begin
  67. Result:=TMyClaims.Create;
  68. end;
  69. procedure TTestJWT.TestSignNone;
  70. Var
  71. P1,P2 : String;
  72. begin
  73. P1:=FJWT.JOSE.AsEncodedString;
  74. P2:=FJWT.Claims.AsEncodedString;
  75. AssertEquals('Signed with none',P1+'.'+P2+'.',FJWT.Sign(TJWTKey.Empty));
  76. end;
  77. procedure TTestJWT.TestVerifyNone;
  78. Var
  79. aJWT : String;
  80. begin
  81. aJWT:=FJWT.AsEncodedString;
  82. FVerifyResult:=TJWT.ValidateJWT(aJWT,TJWTKey.Empty,TMyJWT);
  83. AssertNotNull('Have result',FVerifyResult);
  84. AssertEquals('Correct class',TMyJWT,FVerifyResult.ClassType);
  85. end;
  86. procedure TTestJWT.TestSignSHA256;
  87. Var
  88. Sign,P1,P2 : UTF8String;
  89. aDigest : TSHA256Digest;
  90. B : TBytes;
  91. begin
  92. FJWT.JOSE.alg:='HS256';
  93. // Writeln('JOSE: ',FJWT.JOSE.AsString);
  94. // Writeln('Claims: ',FJWT.Claims.AsString);
  95. P1:=FJWT.JOSE.AsEncodedString;
  96. P2:=FJWT.Claims.AsEncodedString;
  97. B:=TEncoding.UTF8.GetAnsiBytes(P1+'.'+P2);
  98. if not TSHA256.HMAC(FKey.AsPointer,FKey.Length,PByte(B),Length(B),aDigest{%H-}) then
  99. Fail('Could not HMAC');
  100. Sign:=Base64URL.Encode(@aDigest[0],Length(aDigest),False);
  101. // Writeln('Signed: ',P1+'.'+P2+'.'+Sign);
  102. AssertEquals('Signed with SHA256',P1+'.'+P2+'.'+Sign,FJWT.Sign(FKey));
  103. end;
  104. procedure TTestJWT.TestVerifySHA256;
  105. Const
  106. JWTText ='eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.'+
  107. 'eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.'+
  108. 'SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
  109. begin
  110. FKey.AsString:='your-256-bit-secret';
  111. FVerifyResult:=TJWT.ValidateJWT(JWTText,FKey);
  112. AssertNotNull('Have result',FVerifyResult);
  113. AssertEquals('Have correct algorithm','HS256',FVerifyResult.JOSE.Alg);
  114. AssertEquals('Have correct typ','JWT',FVerifyResult.JOSE.typ);
  115. AssertEquals('Have correct sub','1234567890',FVerifyResult.Claims.sub);
  116. end;
  117. procedure TTestJWT.TestSignSHA512;
  118. Var
  119. Sign,P1,P2 : UTF8String;
  120. aDigest : TSHA512Digest;
  121. B : TBytes;
  122. begin
  123. FJWT.JOSE.alg:='HS512';
  124. // Writeln('JOSE: ',FJWT.JOSE.AsString);
  125. // Writeln('Claims: ',FJWT.Claims.AsString);
  126. P1:=FJWT.JOSE.AsEncodedString;
  127. P2:=FJWT.Claims.AsEncodedString;
  128. B:=TEncoding.UTF8.GetAnsiBytes(P1+'.'+P2);
  129. if not TSHA512.HMAC(FKey.AsPointer,FKey.Length,PByte(B),Length(B),aDigest{%H-}) then
  130. Fail('Could not HMAC');
  131. Sign:=Base64URL.Encode(@aDigest[0],Length(aDigest),False);
  132. // Writeln('Signed: ',P1+'.'+P2+'.'+Sign);
  133. AssertEquals('Signed with SHA512',P1+'.'+P2+'.'+Sign,FJWT.Sign(FKey));
  134. end;
  135. procedure TTestJWT.TestVerifySHA512;
  136. Const
  137. JWTText = 'eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.'+
  138. 'eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.'+
  139. 'FEBOl5fjgnPe4gcc5ElXrHDl0jWsshiJ9rS0hlehItc-PKQEzwRKbhcz69V8kwRCUM2rDtuwaXK6DJfO1VOZdw';
  140. begin
  141. FVerifyResult:=TMyJWT.ValidateJWT(JWTText,FKey);
  142. AssertNotNull('Have result',FVerifyResult);
  143. AssertEquals('Correct class',TMyJWT,FVerifyResult.ClassType);
  144. AssertNotNull('Have result.claims',FVerifyResult.Claims);
  145. AssertEquals('Correct claims class',TMyClaims,FVerifyResult.Claims.ClassType);
  146. AssertEquals('Have correct algorithm','HS512',FVerifyResult.JOSE.Alg);
  147. AssertEquals('Have correct typ','JWT',FVerifyResult.JOSE.typ);
  148. AssertEquals('Have correct sub','1234567890',FVerifyResult.Claims.sub);
  149. AssertEquals('Have correct name','John Doe',(TMyJWT(FVerifyResult).Claims as TMyClaims).Name);
  150. AssertEquals('Have correct admin',true,(TMyJWT(FVerifyResult).Claims as TMyClaims).Admin);
  151. end;
  152. procedure TTestJWT.TestSignSHA384;
  153. Var
  154. Sign,P1,P2 : UTF8String;
  155. aDigest : TSHA384Digest;
  156. B : TBytes;
  157. begin
  158. FJWT.JOSE.alg:='HS384';
  159. // Writeln('JOSE: ',FJWT.JOSE.AsString);
  160. // Writeln('Claims: ',FJWT.Claims.AsString);
  161. P1:=FJWT.JOSE.AsEncodedString;
  162. P2:=FJWT.Claims.AsEncodedString;
  163. B:=TEncoding.UTF8.GetAnsiBytes(P1+'.'+P2);
  164. if not TSHA384.HMAC(FKey.AsPointer,FKey.Length,PByte(B),Length(B),aDigest) then
  165. Fail('Could not HMAC');
  166. Sign:=Base64URL.Encode(@aDigest[0],Length(aDigest),False);
  167. // Writeln('Signed: ',P1+'.'+P2+'.'+Sign);
  168. AssertEquals('Signed with SHA384',P1+'.'+P2+'.'+Sign,FJWT.Sign(FKey));
  169. end;
  170. procedure TTestJWT.TestVerifySHA384;
  171. Const
  172. JWTText =
  173. 'eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.'+
  174. 'eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.'+
  175. '8XBKpuFoIEyTxqiP7Rw32VkkxSPGrujBw2ZiKgcX5ZgjH3M8OmTWfYeRDAR6NRVB';
  176. begin
  177. FVerifyResult:=TMyJWT.ValidateJWT(JWTText,FKey);
  178. AssertNotNull('Have result',FVerifyResult);
  179. AssertEquals('Correct class',TMyJWT,FVerifyResult.ClassType);
  180. AssertNotNull('Have result.claims',FVerifyResult.Claims);
  181. AssertEquals('Correct claims class',TMyClaims,FVerifyResult.Claims.ClassType);
  182. AssertEquals('Have correct algorithm','HS384',FVerifyResult.JOSE.Alg);
  183. AssertEquals('Have correct typ','JWT',FVerifyResult.JOSE.typ);
  184. AssertEquals('Have correct sub','1234567890',FVerifyResult.Claims.sub);
  185. AssertEquals('Have correct name','John Doe',(TMyJWT(FVerifyResult).Claims as TMyClaims).Name);
  186. AssertEquals('Have correct admin',true,(TMyJWT(FVerifyResult).Claims as TMyClaims).Admin);
  187. end;
  188. procedure TTestJWT.TestVerifyES256;
  189. Const
  190. // from JWT.IO
  191. aJWT =
  192. 'eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.'+
  193. 'eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.'+
  194. 'tyh-VfuzIxCyGYDlkBA7DfyjrqmSHu6pQ2hoZuFqUSLPNY2N0mpHb3nk5K17HWP_3cYHBw7AhHale5wky6-sVA';
  195. Var
  196. aPrivateKey2: TEccPrivateKey = ($7a,$f6,$73,$2f,$58,$1d,$00,$5a,$fc,$f2,$16,$f6,$38,$5f,$f6,
  197. $37,$10,$29,$24,$2c,$c6,$08,$40,$dd,$7d,$2a,$7a,$55,$03,$b7,
  198. $d2,$1c);
  199. begin
  200. FKey:=TJWTKey.Create(@aPrivateKey2,SizeOf(TEccPrivateKey));
  201. FVerifyResult:=TMyJWT.ValidateJWT(aJWT,FKey);
  202. AssertNotNull('Have result',FVerifyResult);
  203. AssertEquals('Correct class',TMyJWT,FVerifyResult.ClassType);
  204. AssertNotNull('Have result.claims',FVerifyResult.Claims);
  205. AssertEquals('Correct claims class',TMyClaims,FVerifyResult.Claims.ClassType);
  206. AssertEquals('Have correct algorithm','ES256',FVerifyResult.JOSE.Alg);
  207. AssertEquals('Have correct typ','JWT',FVerifyResult.JOSE.typ);
  208. AssertEquals('Have correct sub','1234567890',FVerifyResult.Claims.sub);
  209. AssertEquals('Have correct name','John Doe',(TMyJWT(FVerifyResult).Claims as TMyClaims).Name);
  210. AssertEquals('Have correct admin',true,(TMyJWT(FVerifyResult).Claims as TMyClaims).Admin);
  211. end;
  212. procedure TTestJWT.TestVerifyES256Pem;
  213. Const
  214. aInput =
  215. 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.' +
  216. 'eyJpYXQiOjE1MTYyMzkwMjIsImV4cCI6MTUxNjI0OTAyMiwiaXNzIjoiRGVscGhpIEpPU0UgYW5kIEpXVCBMaWJyYXJ5In0.'+
  217. '4QDMKAvHwb6pA5fN0oQjlzuKmPIlNpmIQ8vPH7zy4fjZdtcPVJMtfiVhztwQldQL9A5yzBKI8q2puVygm-2Adw';
  218. // Private key in PEM format
  219. Const APrivateKeyPem =
  220. '-----BEGIN EC PRIVATE KEY-----'+ #10+
  221. 'MHcCAQEEIFzS3/5bCnrlpa4902/zkYzURF6E2D8pazgnJu4smhpQoAoGCCqGSM49'+ #10+
  222. 'AwEHoUQDQgAEqTjyg2z65i+zbyUZW8BQ+K87DNsICRaEH7Fy7Rm3MseXy9ItSCQU'+ #10+
  223. 'VeJbtO6kYUA00mx7bKoC1sx5sbtFExnYPQ=='+ #10+
  224. '-----END EC PRIVATE KEY-----';
  225. Var
  226. S : TStringStream;
  227. aPrivateKey : TEccPrivateKey;
  228. aPublicKey : TEccPublicKey;
  229. X,Y : AnsiString;
  230. begin
  231. S:=TStringStream.Create(aPrivateKeyPem);
  232. try
  233. PemLoadECDSA(S,aPrivateKey,aPublicKey,X,Y);
  234. finally
  235. S.Free;
  236. end;
  237. FKey:=TJWTKey.Create(@aPrivateKey,SizeOf(TEccPrivateKey));
  238. FVerifyResult:=TMyJWT.ValidateJWT(aInput,FKey);
  239. AssertNotNull('Have result',FVerifyResult);
  240. AssertEquals('Correct class',TMyJWT,FVerifyResult.ClassType);
  241. AssertNotNull('Have result.claims',FVerifyResult.Claims);
  242. AssertEquals('Correct claims class',TMyClaims,FVerifyResult.Claims.ClassType);
  243. AssertEquals('Have correct algorithm','ES256',FVerifyResult.JOSE.Alg);
  244. AssertEquals('Have correct typ','JWT',FVerifyResult.JOSE.typ);
  245. AssertEquals('Have correct sub','',FVerifyResult.Claims.sub);
  246. AssertEquals('Have correct name','',(TMyJWT(FVerifyResult).Claims as TMyClaims).Name);
  247. AssertEquals('Have correct admin',False,(TMyJWT(FVerifyResult).Claims as TMyClaims).Admin);
  248. end;
  249. procedure TTestJWT.TestSignES256;
  250. // Private key in PEM format
  251. Const APrivateKeyPem =
  252. '-----BEGIN EC PRIVATE KEY-----'+ #10+
  253. 'MHcCAQEEIFzS3/5bCnrlpa4902/zkYzURF6E2D8pazgnJu4smhpQoAoGCCqGSM49'+ #10+
  254. 'AwEHoUQDQgAEqTjyg2z65i+zbyUZW8BQ+K87DNsICRaEH7Fy7Rm3MseXy9ItSCQU'+ #10+
  255. 'VeJbtO6kYUA00mx7bKoC1sx5sbtFExnYPQ=='+ #10+
  256. '-----END EC PRIVATE KEY-----';
  257. Var
  258. S : TStringStream;
  259. aPrivateKey : TEccPrivateKey;
  260. aPublicKey : TEccPublicKey;
  261. X,Y : AnsiString;
  262. begin
  263. S:=TStringStream.Create(aPrivateKeyPem);
  264. try
  265. PemLoadECDSA(S,aPrivateKey,aPublicKey,X,Y);
  266. finally
  267. S.Free;
  268. end;
  269. FKey:=TJWTKey.Create(@aPrivateKey,SizeOf(TEccPrivateKey));
  270. FJWT.JOSE.alg:='ES256';
  271. //Writeln('JOSE: ',FJWT.JOSE.AsString);
  272. //Writeln('Claims: ',FJWT.Claims.AsString);
  273. FJWT.Sign(FKey);
  274. //WriteLn('Signature: ',FJWT.Signature);
  275. //WriteLn('JWT: ',FJWT.AsEncodedString);
  276. FVerifyResult := TMyJWT.ValidateJWT(FJWT.AsEncodedString, FKey);
  277. AssertNotNull('Have result',FVerifyResult);
  278. AssertEquals('Correct class',TMyJWT,FVerifyResult.ClassType);
  279. AssertNotNull('Have result.claims',FVerifyResult.Claims);
  280. AssertEquals('Correct claims class',TMyClaims,FVerifyResult.Claims.ClassType);
  281. end;
  282. procedure TTestJWT.TestVerifyRS256Pem;
  283. begin
  284. TestVerifyRSAPem(TJWTSignerRS256);
  285. end;
  286. procedure TTestJWT.TestVerifyRS384Pem;
  287. begin
  288. TestVerifyRSAPem(TJWTSignerRS384);
  289. end;
  290. procedure TTestJWT.TestVerifyRS512Pem;
  291. begin
  292. TestVerifyRSAPem(TJWTSignerRS512);
  293. end;
  294. procedure TTestJWT.TestVerifyRS256_rfc7515;
  295. const
  296. // values from RFC 7515
  297. HeaderJSON = '{"alg":"RS256"}';
  298. HeaderExpected = 'eyJhbGciOiJSUzI1NiJ9';
  299. PayloadJSON = '{"iss":"joe",'#13#10+
  300. ' "exp":1300819380,'#13#10+
  301. ' "http://example.com/is_root":true}';
  302. PayloadExpected = 'eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ';
  303. SignInputExpected: TBytes =
  304. (101, 121, 74, 104, 98, 71, 99, 105, 79, 105, 74, 83, 85, 122, 73,
  305. 49, 78, 105, 74, 57, 46, 101, 121, 74, 112, 99, 51, 77, 105, 79, 105,
  306. 74, 113, 98, 50, 85, 105, 76, 65, 48, 75, 73, 67, 74, 108, 101, 72,
  307. 65, 105, 79, 106, 69, 122, 77, 68, 65, 52, 77, 84, 107, 122, 79, 68,
  308. 65, 115, 68, 81, 111, 103, 73, 109, 104, 48, 100, 72, 65, 54, 76,
  309. 121, 57, 108, 101, 71, 70, 116, 99, 71, 120, 108, 76, 109, 78, 118,
  310. 98, 83, 57, 112, 99, 49, 57, 121, 98, 50, 57, 48, 73, 106, 112, 48,
  311. 99, 110, 86, 108, 102, 81);
  312. RSA_n = 'ofgWCuLjybRlzo0tZWJjNiuSfb4p4fAkd_wWJcyQoTbji9k0l8W26mPddx'+
  313. 'HmfHQp-Vaw-4qPCJrcS2mJPMEzP1Pt0Bm4d4QlL-yRT-SFd2lZS-pCgNMs'+
  314. 'D1W_YpRPEwOWvG6b32690r2jZ47soMZo9wGzjb_7OMg0LOL-bSf63kpaSH'+
  315. 'SXndS5z5rexMdbBYUsLA9e-KXBdQOS-UTo7WTBEMa2R2CapHg665xsmtdV'+
  316. 'MTBQY4uDZlxvb3qCo5ZwKh9kG4LT6_I5IhlJH7aGhyxXFvUK-DWNmoudF8'+
  317. 'NAco9_h9iaGNj8q2ethFkMLs91kzk2PAcDTW9gb54h4FRWyuXpoQ';
  318. RSA_e = 'AQAB';
  319. RSA_d = 'Eq5xpGnNCivDflJsRQBXHx1hdR1k6Ulwe2JZD50LpXyWPEAeP88vLNO97I'+
  320. 'jlA7_GQ5sLKMgvfTeXZx9SE-7YwVol2NXOoAJe46sui395IW_GO-pWJ1O0'+
  321. 'BkTGoVEn2bKVRUCgu-GjBVaYLU6f3l9kJfFNS3E0QbVdxzubSu3Mkqzjkn'+
  322. '439X0M_V51gfpRLI9JYanrC4D4qAdGcopV_0ZHHzQlBjudU2QvXt4ehNYT'+
  323. 'CBr6XCLQUShb1juUO1ZdiYoFaFQT5Tw8bGUl_x_jTj3ccPDVZFD9pIuhLh'+
  324. 'BOneufuBiB4cS98l2SR_RQyGWSeWjnczT0QU91p1DhOVRuOopznQ';
  325. RSA_p = '4BzEEOtIpmVdVEZNCqS7baC4crd0pqnRH_5IB3jw3bcxGn6QLvnEtfdUdi'+
  326. 'YrqBdss1l58BQ3KhooKeQTa9AB0Hw_Py5PJdTJNPY8cQn7ouZ2KKDcmnPG'+
  327. 'BY5t7yLc1QlQ5xHdwW1VhvKn-nXqhJTBgIPgtldC-KDV5z-y2XDwGUc';
  328. RSA_q = 'uQPEfgmVtjL0Uyyx88GZFF1fOunH3-7cepKmtH4pxhtCoHqpWmT8YAmZxa'+
  329. 'ewHgHAjLYsp1ZSe7zFYHj7C6ul7TjeLQeZD_YwD66t62wDmpe_HlB-TnBA'+
  330. '-njbglfIsRLtXlnDzQkv5dTltRJ11BKBBypeeF6689rjcJIDEz9RWdc';
  331. RSA_dp = 'BwKfV3Akq5_MFZDFZCnW-wzl-CCo83WoZvnLQwCTeDv8uzluRSnm71I3Q'+
  332. 'CLdhrqE2e9YkxvuxdBfpT_PI7Yz-FOKnu1R6HsJeDCjn12Sk3vmAktV2zb'+
  333. '34MCdy7cpdTh_YVr7tss2u6vneTwrA86rZtu5Mbr1C1XsmvkxHQAdYo0';
  334. RSA_dq = 'h_96-mK1R_7glhsum81dZxjTnYynPbZpHziZjeeHcXYsXaaMwkOlODsWa'+
  335. '7I9xXDoRwbKgB719rrmI2oKr6N3Do9U0ajaHF-NKJnwgjMd2w9cjz3_-ky'+
  336. 'NlxAr2v4IKhGNpmM5iIgOS1VZnOZ68m6_pbLBSp3nssTdlqvd0tIiTHU';
  337. RSA_qi = 'IYd7DHOhrWvxkwPQsRM2tOgrjbcrfvtQJipd-DlcxyVuuM9sQLdgjVk2o'+
  338. 'y26F0EmpScGLq2MowX7fhd_QJQ3ydy5cY7YIBi87w93IKLEdfnbJtoOPLU'+
  339. 'W0ITrJReOgo1cq9SbsxYawBgfp_gh6A5603k2-ZQwVK0JKSHuLFkuQ3U';
  340. Signature ='cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7'+
  341. 'AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds9uJdbF9CUAr7t1dnZcAcQjbKBYNX4'+
  342. 'BAynRFdiuB--f_nZLgrnbyTyWzO75vRK5h6xBArLIARNPvkSjtQBMHlb1L07Qe7K'+
  343. '0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZESc6BfI7noOPqv'+
  344. 'hJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrB'+
  345. 'p0igcN_IoypGlUPQGe77Rw';
  346. var
  347. HeaderEncoded, PayloadEncoded, SignInput, aInput: String;
  348. X509RSAPrivateKey: TX509RSAPrivateKey;
  349. X509RSAPublicKey: TX509RSAPublicKey;
  350. RSA: TRSA;
  351. begin
  352. HeaderEncoded:=Base64URL.Encode(HeaderJSON,false);
  353. AssertEquals('Header',HeaderExpected,HeaderEncoded);
  354. PayloadEncoded:=Base64URL.Encode(PayloadJSON,false);
  355. AssertEquals('Payload',PayloadExpected,PayloadEncoded);
  356. SignInput:=HeaderEncoded+'.'+PayloadEncoded;
  357. if (length(SignInput)<>length(SignInputExpected))
  358. or not CompareMem(@SignInput[1],@SignInputExpected[0],length(SignInput)) then
  359. Fail('SignInput');
  360. X509RSAPrivateKey.InitWithBase64UrlEncoded(RSA_n,RSA_e,RSA_d,RSA_p,RSA_q,RSA_dp,RSA_dq,RSA_qi);
  361. X509RSAPublicKey.InitWithBase64UrlEncoded(RSA_n,RSA_e);
  362. RSACreate(RSA);
  363. try
  364. RSAInitFromPublicKey(RSA,X509RSAPublicKey);
  365. FKey.AsBytes:=X509RSAPublicKey.AsDER;
  366. aInput:=SignInput+'.'+Signature;
  367. // verify
  368. FVerifyResult:=TMyJWT.ValidateJWT(aInput,FKey);
  369. AssertNotNull('Have result',FVerifyResult);
  370. AssertEquals('Correct class',TMyJWT,FVerifyResult.ClassType);
  371. AssertNotNull('Have result.claims',FVerifyResult.Claims);
  372. AssertEquals('Correct claims class',TMyClaims,FVerifyResult.Claims.ClassType);
  373. AssertEquals('Have correct algorithm','RS256',FVerifyResult.JOSE.Alg);
  374. AssertEquals('Have correct typ','',FVerifyResult.JOSE.typ);
  375. AssertEquals('Have correct iss','joe',FVerifyResult.Claims.iss);
  376. AssertEquals('Have correct exp',1300819380,FVerifyResult.Claims.exp);
  377. finally
  378. RSAFree(RSA);
  379. end;
  380. end;
  381. procedure TTestJWT.TestI2OSP;
  382. procedure t(c: DWord; Len: integer; const Expected: string);
  383. var
  384. Actual: String;
  385. begin
  386. Actual:=I2OSP(c,Len);
  387. if Actual<>Expected then
  388. Fail('I2OSP('+IntToStr(c)+','+IntToStr(Len)+') expected "'+StringToHex(Expected)+'", but got "'+StringToHex(Actual)+'"');
  389. end;
  390. begin
  391. t(0,0,'');
  392. t(0,1,#0);
  393. t(1,1,#1);
  394. t(1,2,#0#1);
  395. t(258,2,#1#2);
  396. t($10203,3,#1#2#3);
  397. t($1020304,4,#1#2#3#4);
  398. t($ffffffff,4,#255#255#255#255);
  399. end;
  400. procedure TTestJWT.TestMGF1SHA1;
  401. procedure t(const InputStr: string; Len: integer; const ExpectedHex: String);
  402. var
  403. ActualHex: string;
  404. begin
  405. ActualHex:=StringToHex(MGF1SHA1(InputStr,Len));
  406. if ActualHex<>ExpectedHex then
  407. Fail('MGF1SHA1('+StringToHex(InputStr)+','+IntToStr(Len)+') expected "'+ExpectedHex+'", but got "'+ActualHex+'"');
  408. end;
  409. begin
  410. t('foo',3,'1AC907');
  411. t('foo',5,'1AC9075CD4');
  412. t('bar',5,'BC0C655E01');
  413. t('bar',50,'BC0C655E016BC2931D85A2E675181ADCEF7F581F76DF2739DA74FAAC41627BE2F7F415C89E983FD0CE80CED9878641CB4876');
  414. end;
  415. procedure TTestJWT.TestMGF1SHA256;
  416. procedure t(const InputStr: string; Len: integer; const ExpectedHex: String);
  417. var
  418. ActualHex: string;
  419. begin
  420. ActualHex:=StringToHex(MGF1SHA256(InputStr,Len));
  421. if ActualHex<>ExpectedHex then
  422. Fail('MGF1SHA256('+StringToHex(InputStr)+','+IntToStr(Len)+') expected "'+ExpectedHex+'", but got "'+ActualHex+'"');
  423. end;
  424. begin
  425. t('bar',1,'38');
  426. t('bar',50,'382576A7841021CC28FC4C0948753FB8312090CEA942EA4C4E735D10DC724B155F9F6069F289D61DACA0CB814502EF04EAE1');
  427. end;
  428. procedure TTestJWT.TestVerifyPS256;
  429. const
  430. HeaderJSON = '{"alg":"PS256"}';
  431. HeaderExpected = 'eyJhbGciOiJQUzI1NiJ9';
  432. PayloadJSON = 'In our village, folks say God crumbles up the old moon into stars.';
  433. PayloadExpected = 'SW4gb3VyIHZpbGxhZ2UsIGZvbGtzIHNheSBHb2QgY3J1bWJsZXMgdXAgdGhlIG9sZCBtb29uIGludG8gc3RhcnMu';
  434. RSA_n = 'ofgWCuLjybRlzo0tZWJjNiuSfb4p4fAkd_wWJcyQoTbji9k0l8W26mPddx'+
  435. 'HmfHQp-Vaw-4qPCJrcS2mJPMEzP1Pt0Bm4d4QlL-yRT-SFd2lZS-pCgNMs'+
  436. 'D1W_YpRPEwOWvG6b32690r2jZ47soMZo9wGzjb_7OMg0LOL-bSf63kpaSH'+
  437. 'SXndS5z5rexMdbBYUsLA9e-KXBdQOS-UTo7WTBEMa2R2CapHg665xsmtdV'+
  438. 'MTBQY4uDZlxvb3qCo5ZwKh9kG4LT6_I5IhlJH7aGhyxXFvUK-DWNmoudF8'+
  439. 'NAco9_h9iaGNj8q2ethFkMLs91kzk2PAcDTW9gb54h4FRWyuXpoQ';
  440. RSA_e = 'AQAB';
  441. SignatureEncoded =
  442. 'TRWhwRo5dMv9-8OzrInfJTwmUGYgjLfHk8lqF072ND-FmLWEBnUTOpY8oJXp'
  443. +'8FdWw2SalbdOeNlrtlJjwk4XK8Ql2iJ_2qMCtxsvLPhKBOqFoAF4aBvTOEDV'
  444. +'JDxf0DaBSiydEEtfTVV2iwBcjWabu5J2XieR5y7QZQtuHsn7T3qKBvCcCejN'
  445. +'3Y2oqAT3qMHvu1fTms1r_91wBn_K7Wjd9UkZ1n02qQcUHJznR_OF2BgN7_KW'
  446. +'IDAF9ZS9keoju2NPpPelO4yxa2XUPnehY3G7dHKoCxUEQR4d2Xc5voqDASTV'
  447. +'CDqQS4PVOZdvT3Ein6-SanAlCwbWBbkvT8g6-5PImQ';
  448. var
  449. X509RSAPublicKey: TX509RSAPublicKey;
  450. RSA: TRSA;
  451. HeaderEncoded, PayloadEncoded, SignInput: String;
  452. r: Int64;
  453. Signature: TBytes;
  454. begin
  455. // RSASSA-PSS using SHA-256 and MGF1 with SHA-256
  456. HeaderEncoded:=Base64URL.Encode(HeaderJSON,false);
  457. AssertEquals('Header',HeaderExpected,HeaderEncoded);
  458. PayloadEncoded:=Base64URL.Encode(PayloadJSON,false);
  459. AssertEquals('Payload',PayloadExpected,PayloadEncoded);
  460. SignInput:=HeaderEncoded+'.'+PayloadEncoded;
  461. // load public key
  462. X509RSAPublicKey.InitWithBase64UrlEncoded(RSA_n,RSA_e);
  463. RSACreate(RSA);
  464. try
  465. RSAInitFromPublicKey(RSA,X509RSAPublicKey);
  466. AssertEquals('RSA.ModulusLen',256,RSA.ModulusLen);
  467. AssertEquals('RSA.ModulusBits',2048,RSA.ModulusBits);
  468. Signature:=Base64URL.Decode(SignatureEncoded,false);
  469. AssertEquals('length(Signature)',RSA.ModulusLen,length(Signature));
  470. r:=RSASSA_PS256_Verify(RSA,@SignInput[1],length(SignInput),@Signature[0]);
  471. AssertEquals('RSASSA_PS256_Verify',0,r);
  472. finally
  473. RSAFree(RSA);
  474. end;
  475. end;
  476. procedure TTestJWT.TestVerifyPS256Pem;
  477. begin
  478. TestVerifyRSAPem(TJWTSignerPS256);
  479. end;
  480. procedure TTestJWT.TestVerifyPS384Pem;
  481. begin
  482. TestVerifyRSAPem(TJWTSignerPS384);
  483. end;
  484. procedure TTestJWT.TestVerifyPS512Pem;
  485. begin
  486. TestVerifyRSAPem(TJWTSignerPS512);
  487. end;
  488. procedure TTestJWT.SetUp;
  489. begin
  490. Inherited;
  491. FKey:=TJWTKey.Create('mysecretkey');
  492. FJWT:=TMyJWT.Create;
  493. FJWT.JOSE.alg:='none';
  494. FJWT.JOSE.typ:='JWT';
  495. FJWT.Claims.sub:='1234567890';
  496. FJWT.Claims.iat:=1516239022;
  497. (FJWT.Claims as TMyClaims).Name:='John Doe';
  498. end;
  499. procedure TTestJWT.TearDown;
  500. begin
  501. FreeAndNil(FJWT);
  502. FreeAndNil(FVerifyResult);
  503. Inherited;
  504. end;
  505. procedure TTestJWT.GetTestPEM(out aPrivateKeyPEM, aPublicKeyPEM: string);
  506. const
  507. // generated with
  508. // openssl genrsa -out private.pem 2048
  509. PrivateKeyPem =
  510. '-----BEGIN RSA PRIVATE KEY-----'#10+
  511. 'MIIEpQIBAAKCAQEAvkRfGW8psCZ3G4+hBA6W/CR/FHhBLB3k3QLypamPbRFlFBxL'#10+
  512. 'tOK2NblBybY22vUiMLZbb5x8OoOj/IhOrJAlTqhtbTWLy/0K3qbG09vLm8V40kEK'#10+
  513. '8/p0STrp3UmsxHNkccj9MRSKk7pOyEvxSCY6K5JGK1VTsMuDCS7DCYk6Vqr3zjX7'#10+
  514. 'qedF1PVM+Z5t0B+f//kt3oBETNlic4IooEpG/PN2GUQ0oZpa16DDtfgGu7wT3X3Q'#10+
  515. 'EZFWLJYQTvGc82NpachBIUvqNdIt1npbK38MXU4IPHVrSN/HdK2nQPSMLdKnTV+E'#10+
  516. 'h/HcxpfjBjarg+VjgDqlmqJ9bkosOVn35vsg8wIDAQABAoIBAQCZxVwujB7fFFdS'#10+
  517. '2QPC6Z+w7DYgbwgNBaP/0vAUXzNhbJuKY0v0Rv4H8U9wHGm9EDyvrdG8JHZqPBX+'#10+
  518. 'dJNQ97aPGaRGjO4M0NdGFve+JXcqz6/UDWkywYnV3V1A0NhmdPQK2et3DSjqN7qQ'#10+
  519. 'OoAoVWzR5gf74Zwf2Hpwo3BRdqzFeUYVDOH7e7q1SOf2QeU54kVUG21saJR0wsyH'#10+
  520. 'oSX8BMU2kmg1Un8ET4FM5xEwhdTZzgFTJVZhc6EfOKVbQt6cKmW3aER3c9vR7M3l'#10+
  521. 'N6Oq73vqrfmy+jFMwz1SoPObQQ7UAnr7YUowaX0AzxHpYm/afyVm+Toym0qWGrrY'#10+
  522. 'MY/l+vNRAoGBAOsi72pJj30ApfVbSpx8/8QIpweLbEgAD+Ssd41Kgc4O/N7azB61'#10+
  523. 'RjzSOs1BGhpAZNU6muAAbucm9EssfG5WTAjIM2W2LVuZXXEVXqEGkIymPz9NGugf'#10+
  524. 'JaCWLaoibmwHkKa+ZV9kDwasmx/VkbAfAbRWaz49ejdrMmkpCW77lYjHAoGBAM8m'#10+
  525. 'PVJWvFhQrB21xQGSWKd5iSUn2V92gICeDoORqfVtt/UPOaDT915KzXPh4bJeOwg6'#10+
  526. 'Kkx5wX6UwaNSRH39loDSY1rsBYioV8bxW0BpBvEJG7KXRbBvxzr0+TJkCHgmGMns'#10+
  527. 'dhePYUcriCaqpQi1yzf201oLTZ6PlJxkmHQobXJ1AoGBAIgWPg576InmWCa64WHU'#10+
  528. 'joq8nz8kmFTLhGdK0h56IspJrlyksUKMk8wbuGCW7y6GWlV2h7BhT86Eoxrm8lVB'#10+
  529. 'qNvkUqrpVzMOfiA2x//WNs7QYQaX75ysejCI+oDfUJ1Be5yl0TH2TSQFvfoctycB'#10+
  530. 'qxDee08YcaWlaxWl5InRHeh9AoGABm3XZWDPw6XtUZa8oIncOoZpHUAZXP8eid9d'#10+
  531. '7/NrZPScyvxH+5fYi5Kiwb/280Q9bMnxWiJFQRp40ArTmV1veFwPPVkp6s3eu4vu'#10+
  532. 'GxenYX+43lgXj5xIgKntugSkxqXYCxxNpfmLOVw+g4S0Torl3bzJXngPVqZ6JEhy'#10+
  533. '+tfuXakCgYEA19/JCD/5pVPJtwyDDAYnUUESK+JfBPq1cTbsxcOq01mp5ntsqR4y'#10+
  534. 'dtOAmxMASvsqud3XIM5fO5m3Jpl1phiGhCw4nvVLcYzVWxYY+oWoeCSyECgu5tmT'#10+
  535. 'Fo8vn4EEXCkEAA2YPiEuVcrcYsWkLivCTC19lJDfUNMmpwSdiGz/tDU='#10+
  536. '-----END RSA PRIVATE KEY-----'#10;
  537. PublicKeyPem =
  538. '-----BEGIN PUBLIC KEY-----'#10+
  539. 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvkRfGW8psCZ3G4+hBA6W'#10+
  540. '/CR/FHhBLB3k3QLypamPbRFlFBxLtOK2NblBybY22vUiMLZbb5x8OoOj/IhOrJAl'#10+
  541. 'TqhtbTWLy/0K3qbG09vLm8V40kEK8/p0STrp3UmsxHNkccj9MRSKk7pOyEvxSCY6'#10+
  542. 'K5JGK1VTsMuDCS7DCYk6Vqr3zjX7qedF1PVM+Z5t0B+f//kt3oBETNlic4IooEpG'#10+
  543. '/PN2GUQ0oZpa16DDtfgGu7wT3X3QEZFWLJYQTvGc82NpachBIUvqNdIt1npbK38M'#10+
  544. 'XU4IPHVrSN/HdK2nQPSMLdKnTV+Eh/HcxpfjBjarg+VjgDqlmqJ9bkosOVn35vsg'#10+
  545. '8wIDAQAB'#10+
  546. '-----END PUBLIC KEY-----';
  547. begin
  548. aPrivateKeyPEM:=PrivateKeyPem;
  549. aPublicKeyPEM:=PublicKeyPem;
  550. end;
  551. procedure TTestJWT.TestVerifyRSAPem(SignerClass: TJWTSignerClass);
  552. var
  553. aInput, aPrivateKeyPEM, aPublicKeyPEM: String;
  554. Signer: TJWTSigner;
  555. NewDER: TBytes;
  556. RSAPublic: TX509RSAPublicKey;
  557. RSAPrivate: TX509RSAPrivateKey;
  558. begin
  559. GetTestPEM(aPrivateKeyPEM, aPublicKeyPEM);
  560. // header
  561. jwt.JOSE.alg:=SignerClass.AlgorithmName;
  562. // claims
  563. jwt.Claims.exp:=DateTimeToUnix(Now+10);
  564. jwt.Claims.iss:='FPC JWT';
  565. // load private key from pem
  566. FKey.AsBytes:=PemToDER(APrivateKeyPem,_BEGIN_RSA_PRIVATE_KEY,_END_RSA_PRIVATE_KEY);
  567. X509RsaPrivateKeyInitFromDER(RSAPrivate,FKey.AsBytes);
  568. NewDER:=RSAPrivate.AsDER;
  569. if (length(FKey.AsBytes)<>length(NewDER)) or
  570. not CompareMem(@FKey.AsBytes[0],@NewDER[0],length(NewDER)) then
  571. Fail('TX509RSAPrivateKey.AsDER');
  572. // sign
  573. Signer:=TJWTSigner(SignerClass.Create);
  574. try
  575. aInput:=Signer.AppendSignature(JWT,Key);
  576. finally
  577. Signer.Free;
  578. end;
  579. // load public key from pem
  580. FKey.AsBytes:=PemToDER(APublicKeyPem,_BEGIN_PUBLIC_KEY,_END_PUBLIC_KEY);
  581. X509RsaPublicKeyInitFromDER(RSAPublic,FKey.AsBytes);
  582. NewDER:=RSAPublic.AsDER;
  583. if (length(FKey.AsBytes)<>length(NewDER)) or
  584. not CompareMem(@FKey.AsBytes[0],@NewDER[0],length(NewDER)) then
  585. Fail('TX509RSAPublicKey.AsDER');
  586. // verify
  587. FVerifyResult:=TMyJWT.ValidateJWT(aInput,FKey);
  588. AssertNotNull('Have result',FVerifyResult);
  589. AssertEquals('Correct class',TMyJWT,FVerifyResult.ClassType);
  590. AssertNotNull('Have result.claims',FVerifyResult.Claims);
  591. AssertEquals('Correct claims class',TMyClaims,FVerifyResult.Claims.ClassType);
  592. AssertEquals('Have correct algorithm',SignerClass.AlgorithmName,FVerifyResult.JOSE.Alg);
  593. AssertEquals('Have correct typ','JWT',FVerifyResult.JOSE.typ);
  594. AssertEquals('Have correct sub','1234567890',FVerifyResult.Claims.sub);
  595. AssertEquals('Have correct name','John Doe',(TMyJWT(FVerifyResult).Claims as TMyClaims).Name);
  596. AssertEquals('Have correct admin',False,(TMyJWT(FVerifyResult).Claims as TMyClaims).Admin);
  597. end;
  598. initialization
  599. RegisterTest(TTestJWT);
  600. end.