1 /** 2 * Module defining various test cases used in separate implementations. 3 */ 4 module jwtlited.tests; 5 6 version (assert): 7 8 import jwtlited; 9 10 enum Test 11 { 12 decode = 1, 13 encode = 2, 14 15 all = decode | encode 16 } 17 18 enum Valid 19 { 20 key = 1, 21 decode = 2, 22 encode = 4, 23 24 none = 0, 25 all = key | decode | encode 26 } 27 28 struct TestCase 29 { 30 string name; 31 JWTAlgorithm alg; 32 Test test; 33 Valid valid; 34 string key; 35 string pkey; 36 string payload; 37 string token; 38 } 39 40 enum EC256_PUBKEY = `-----BEGIN PUBLIC KEY----- 41 MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEMlFGAIxe+/zLanxz4bOxTI6daFBk 42 NGyQ+P4bc/RmNEq1NpsogiMB5eXC7jUcD/XqxP9HCIhdRBcQHx7aOo3ayQ== 43 -----END PUBLIC KEY-----`; 44 45 enum EC256_PRIVKEY = `-----BEGIN EC PRIVATE KEY----- 46 MHcCAQEEILvM6E7mLOdndALDyFc3sOgUTb6iVjgwRBtBwYZngSuwoAoGCCqGSM49 47 AwEHoUQDQgAEMlFGAIxe+/zLanxz4bOxTI6daFBkNGyQ+P4bc/RmNEq1NpsogiMB 48 5eXC7jUcD/XqxP9HCIhdRBcQHx7aOo3ayQ== 49 -----END EC PRIVATE KEY-----`; 50 51 enum EC512_PUBKEY = `-----BEGIN PUBLIC KEY----- 52 MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBb6j/jJ/+D25Ez8GuU3Al+DsnuZXF 53 AkU6V2eMMezgJu/6E0+FCUMcLaIi6MAuBo74FE6MSCCW4CQ3MGOG75jy67EBENM7 54 xGFOBWlBedku4S7N4cayJbpHVnxc+Z5uK50gchiUripQ1i78wi7W+5WjYYQBUE4l 55 4et/HP21c5n4LRCeasw= 56 -----END PUBLIC KEY-----`; 57 58 enum EC512_PRIVKEY = `-----BEGIN EC PRIVATE KEY----- 59 MIHcAgEBBEIATbhPa+N94KjXbTzHx3ujwN+TwLVlQjyxA2e1jp8oYxogg8S/ceXU 60 CsS/169A1zf1EYKe7lEYm3LTSXcvdaXzT1ygBwYFK4EEACOhgYkDgYYABAFvqP+M 61 n/4PbkTPwa5TcCX4Oye5lcUCRTpXZ4wx7OAm7/oTT4UJQxwtoiLowC4GjvgUToxI 62 IJbgJDcwY4bvmPLrsQEQ0zvEYU4FaUF52S7hLs3hxrIlukdWfFz5nm4rnSByGJSu 63 KlDWLvzCLtb7laNhhAFQTiXh638c/bVzmfgtEJ5qzA== 64 -----END EC PRIVATE KEY-----`; 65 66 enum RS_PRIVKEY = `-----BEGIN RSA PRIVATE KEY----- 67 MIIEowIBAAKCAQEA0hZf4ct1tvPkcqM7826L89TwCPBhuycWMn3xT+4MLeUqe51F 68 LMSm1VK6k+Ew8jrmQ9T9tOtp2vRJFEhoK/WVAb44Sg0PuX9zDLw8ncgW9jON4q6X 69 m9MeJNC2Mb5ogwc72S+kQWNWi3nNR9xrCXmczHfoolVC9/lnU0T+Tp7kj4MZNmSC 70 Cx0eACHpkZV5306TAs+FSlOpKgTL9Wazf1i7teTddkhD5Csm/OE5gBqdAQDqO/8q 71 aKDpmYJTdrjM8RebBq9eTuc5sp7zzIGH2hjveiBG7+/83dDgLwW5IUV1+EB/VqSx 72 jrlurQcH38zYfmXV65QCToJXbF5X3asUluSu9wIDAQABAoIBAQCfXV2qeJ55BBW9 73 aFnn1WnQsyzKex6Hy6So9KSDD36pqfdKAgkhZqNvmuvxlZd9iHR37C/wd8u6zihJ 74 fIuZHRfFVLh6Y+ITwrxRYtFQlyHj7UOqOurCx6lMIA61OU0qZ+hcXilpeKOD9gdk 75 ha2kaF4rNKKB0c+VL9nTbrjChwG2YkneqROL7KyszVHAumU9sZUtaYsxKvwALwZi 76 7GStXCa8yFb0AXuTANWzVQt5QsFvIO5GpXjQrmYJM36pwzKNVKBFCqrMrRoQhuwe 77 UfXOI/VF1tUM9BhZ78R/ccxBGyklQCJt2wO1GqnWKH1lUDHUTDv//V3kI4TF8Tba 78 lEn4l8fhAoGBAPYIVsjDZdi7LTnkXENlUTf+VvWGwM7Upb7QK0LK6rZkJrFeiLfT 79 vPd4TDEcNHcWVKz+dZubJ5m1rC8hh4IUsQv5CcZdcQuJ/dINZyPRyNkNU4O+kDmf 80 50xemRMm9JwpvJfSRsIzoFizzwNsvYeJpQm5ZbGHdVxM1kQBt0P05Hk/AoGBANqZ 81 PWLTcKh942GXDzlr6sg4067neYg5fKMeUU6QsDN5Zf6MmPBNDDVd5+oMTjxRQiSW 82 Q4SIqR2ssDDuowBGBSoAirQyTdiQ/lVo4/h9oQJX2fDEQvMsPSaby6MBzl9kSSPz 83 fBeqSM5fCt6HpkLvzIwS6AlQ4lFzj3fU7tZ3vuRJAoGAGr6FUIWNCKYwIF7meJ0G 84 2yNWqJHhW5pZ+gf+69/K69CvNBCmo/TsUapN/fim61sOEVAH0MZo45iQAv+OD2HY 85 bQjBO0LlCvARG0hBse8X+iAst+F7JAhxyCdwVFijtmYDDi3ZazrZb0r8cc7cO2OH 86 ASuaFlY3N7VShUn6dfSk8VkCgYB0RawUI9k5lfRbFUlgxpkUNL3Lu422OrWj4d1n 87 h6hhSMJKmihDMQg8Xp2brT3z8VjYMyDonvQtN4xkCpqi65uVksI0RMmJVt4hOfCA 88 XPpGT8o5uXrO84n3PkkbhDtsG+CXgcxQnh+pvX3/jXGPCxPmsavAQMiQgIIgQB9l 89 7j2YGQKBgERkwz7s29PN9jg/9D0UGynxhkvJhIo8EcN42/lrnr4MziHxIHN5CwBv 90 oNHVKMZXklzzZ7X2jZcqY5UbTIOwiDonwmjfch8SSHt4L50MIzaCrxzDaEQ//zd6 91 qT7bwBrcVfn7JUE8RRk5qEn5Z81Z/4AciYBFbsOowA/1NDhLoCZ5 92 -----END RSA PRIVATE KEY-----`; 93 94 enum RS_PUBKEY = `-----BEGIN PUBLIC KEY----- 95 MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0hZf4ct1tvPkcqM7826L 96 89TwCPBhuycWMn3xT+4MLeUqe51FLMSm1VK6k+Ew8jrmQ9T9tOtp2vRJFEhoK/WV 97 Ab44Sg0PuX9zDLw8ncgW9jON4q6Xm9MeJNC2Mb5ogwc72S+kQWNWi3nNR9xrCXmc 98 zHfoolVC9/lnU0T+Tp7kj4MZNmSCCx0eACHpkZV5306TAs+FSlOpKgTL9Wazf1i7 99 teTddkhD5Csm/OE5gBqdAQDqO/8qaKDpmYJTdrjM8RebBq9eTuc5sp7zzIGH2hjv 100 eiBG7+/83dDgLwW5IUV1+EB/VqSxjrlurQcH38zYfmXV65QCToJXbF5X3asUluSu 101 9wIDAQAB 102 -----END PUBLIC KEY-----`; 103 104 /// Test cases to test correct validation and signature used with all implementations 105 immutable TestCase[] testCases = [ 106 // NONE 107 TestCase( 108 "NONE - valid", 109 JWTAlgorithm.none, 110 Test.all, Valid.all, 111 null, null, 112 `{"sub":"1234567890","name":"John Doe","iat":1516239022}`, 113 "eyJhbGciOiJub25lIn0.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.", 114 ), 115 TestCase( 116 "NONE - unexpected signature in token", 117 JWTAlgorithm.none, 118 Test.decode, Valid.none, 119 null, null, null, 120 "eyJhbGciOiJub25lIn0.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.B02AWclotXRccJUoyHFSpYHMfg4gUvy4cvFrqwMracg", 121 ), 122 TestCase( 123 "NONE - valid token with different algorithm", 124 JWTAlgorithm.none, 125 Test.decode, Valid.none, 126 null, null, null, 127 "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.B02AWclotXRccJUoyHFSpYHMfg4gUvy4cvFrqwMracg", 128 ), 129 TestCase("NONE - invalid token 1", JWTAlgorithm.none, Test.decode, Valid.none, null, null, null, "aa.bb.cc"), 130 TestCase("NONE - invalid token 2", JWTAlgorithm.none, Test.decode, Valid.none, null, null, null, "aa.bb."), 131 TestCase("NONE - invalid token 3", JWTAlgorithm.none, Test.decode, Valid.none, null, null, null, "aa.bb"), 132 TestCase("NONE - invalid token 4", JWTAlgorithm.none, Test.decode, Valid.none, null, null, null, "aa."), 133 TestCase("NONE - invalid token 5", JWTAlgorithm.none, Test.decode, Valid.none, null, null, null, "aa"), 134 135 // HMAC 136 TestCase( 137 "HS256 - valid decode", 138 JWTAlgorithm.HS256, 139 Test.decode, Valid.all, 140 "FOO BAR BAZ", null, 141 `{"sub":"1234567890","name":"John Doe","iat":1516239022}`, 142 "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.B02AWclotXRccJUoyHFSpYHMfg4gUvy4cvFrqwMracg", 143 ), 144 TestCase( 145 "HS256 - valid all", 146 JWTAlgorithm.HS256, 147 Test.all, Valid.all, 148 "FOO BAR BAZ", null, 149 `{"sub":"1234567890","name":"John Doe","iat":1516239022}`, 150 "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.9hBU4ACaixzXiNcKiy3P04e4dQmrqVD9gAHVpQoUuBM", 151 ), 152 TestCase( 153 "HS256 - not matching signature - sig", 154 JWTAlgorithm.HS256, 155 Test.decode, Valid.key, 156 "FOO BAR BAZ", null, null, 157 "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.B12AWclotXRccJUoyHFSpYHMfg4gUvy4cvFrqwMracg", 158 ), 159 TestCase( 160 "HS256 - not matching signature - pay", 161 JWTAlgorithm.HS256, 162 Test.decode, Valid.key, 163 "FOO BAR BAZ", null, null, 164 "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzcWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.B02AWclotXRccJUoyHFSpYHMfg4gUvy4cvFrqwMracg", 165 ), 166 TestCase( 167 "HS256 - not matching signature - key", 168 JWTAlgorithm.HS256, 169 Test.decode, Valid.key, 170 "FOOBARBAZ", null, null, 171 "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.B02AWclotXRccJUoyHFSpYHMfg4gUvy4cvFrqwMracg", 172 ), 173 174 // RSA 175 TestCase( 176 "RS512 - valid", 177 JWTAlgorithm.RS512, 178 Test.all, Valid.all, RS_PUBKEY, RS_PRIVKEY, 179 `{"foo":42}`, 180 "eyJhbGciOiJSUzUxMiJ9.eyJmb28iOjQyfQ.HawDMFOZBczB6PozCDPMG4LVYqiy9UaOghs7-ccfQfOpm8nnUNjIcC7-_bUi_KK7tcKUqPIjnz7aR89szjNu5-CtiAef62xfbsPq6ZCTh0x57GNHHMa1DBYoLkvMeRmbKdFgEcamh6qEIoGiihGoe11O3J1JoX6-bktDHNmHEBAYK1BzyxLo2yYwYBPa6GoxOV-qM1ysX_4FEqIul9EpAahLX1S5H9MYwiWt4e7yhRYyJgLXM2qPNCzXrVG28QcF5dSnDjzzvdi8urScBFmVnFKWhZrQj3VbvwOUkJFbaYd4eN2rzTBNQV2YQjU9KZL0kJMn2AcnTeyG_IBj34E8Ig" 181 ), 182 TestCase( 183 "RS512 - different alg", 184 JWTAlgorithm.RS512, 185 Test.decode, Valid.key, RS_PUBKEY, RS_PRIVKEY, 186 `{"foo":42}`, 187 "eyJhbGciOiJSUzI1NiJ9.eyJmb28iOjQyfQ.BOHqKf2MlxWvUNEm8TQIlJ2XIigRN9Mesgt88VrpbnjPBhi5r3J_82pi-iX60RscddpXkFS5f0YZxB-rDslPaZEh_P12SXN9QCEuqspeh7_uFbZpG0E2RcKpjvs4N4UnOetEVkcn1GxjZoZEkzffH-tNGFlLQeRVyJPuk1D8cH13JsKbpmqGQayQOmlZjYsdM48Yy0Rb5Pcc0tR95zBXtQtkgeBNYNEBN5lfWaLBpTADm-fJV5dw4Hu3qKvOMOzW2CslIsCTq-1c-VT4I6-hSBlhy2GkozhKKtbGS6G4RpunolX5GOaPiqhKcQx12TnBVqotRMfEJKazkZe3MA80QA" 188 ), 189 TestCase( 190 "RS256 - valid", 191 JWTAlgorithm.RS256, 192 Test.all, Valid.all, RS_PUBKEY, RS_PRIVKEY, 193 `{"foo":42}`, 194 "eyJhbGciOiJSUzI1NiJ9.eyJmb28iOjQyfQ.BOHqKf2MlxWvUNEm8TQIlJ2XIigRN9Mesgt88VrpbnjPBhi5r3J_82pi-iX60RscddpXkFS5f0YZxB-rDslPaZEh_P12SXN9QCEuqspeh7_uFbZpG0E2RcKpjvs4N4UnOetEVkcn1GxjZoZEkzffH-tNGFlLQeRVyJPuk1D8cH13JsKbpmqGQayQOmlZjYsdM48Yy0Rb5Pcc0tR95zBXtQtkgeBNYNEBN5lfWaLBpTADm-fJV5dw4Hu3qKvOMOzW2CslIsCTq-1c-VT4I6-hSBlhy2GkozhKKtbGS6G4RpunolX5GOaPiqhKcQx12TnBVqotRMfEJKazkZe3MA80QA" 195 ), 196 197 // ECDSA 198 TestCase( 199 "ES256 - valid", 200 JWTAlgorithm.ES256, 201 Test.all, Valid.all, EC256_PUBKEY, EC256_PRIVKEY, 202 `{"foo":42}`, 203 "eyJhbGciOiJFUzI1NiJ9.eyJmb28iOjQyfQ.R_MeWV0nLqRcNk9OrczuhykhKJn2wBZIgmwF87TivMlLGk2KB4Ekec9aXz0dOxBfYQflP6PwdSNjgLdYMECwRA" 204 ), 205 TestCase( 206 "ES512 - valid", 207 JWTAlgorithm.ES512, 208 Test.all, Valid.all, EC512_PUBKEY, EC512_PRIVKEY, 209 `{"foo":42}`, 210 "eyJhbGciOiJFUzUxMiJ9.eyJmb28iOjQyfQ.AGjGpLTYdQB2U2amD6-zJAWI0buCUiKgu-hT_JJgDmqyXYjSvJRQ3uaWID3DWj5fISsMoFZNdp29Pn8Rzwn4yWXYADX_4H4OLUb-IZ82qDfVgZNVMlygrvevbczGU-v1FpKac5Ov2CC7irEoCgus-kVhgFe2XscCz5T6UxUmn4V59Jc3" 211 ), 212 ]; 213 214 void evalTest(H)(auto ref H handler, ref immutable(TestCase) tc) @safe 215 { 216 import std.algorithm : countUntil; 217 import std.range : retro; 218 import std.stdio : writeln; 219 // writeln("Test case: ", tc.name); 220 scope (success) writeln("Test case PASSED: ", tc.name); 221 scope (failure) writeln("Test case FAILED: ", tc.name); 222 223 char[512] buf; 224 if (tc.test & Test.decode) 225 { 226 assert(handler.decode(tc.token, buf[]) == !!(tc.valid & Valid.decode)); 227 assert(!(tc.valid & Valid.decode) || buf[0..tc.payload.length] == tc.payload); 228 if (tc.valid & Valid.decode) 229 assert(handler.decode(tc.token, null)); // test validation without payload decode 230 } 231 232 if (tc.test & Test.encode) 233 { 234 immutable len = handler.encode(buf[], tc.payload); 235 if (tc.valid & Valid.encode) 236 { 237 assert(len == tc.token.length); 238 239 // some algorithms generates different signature, so we check if it's valid and only part vithout the signature equals 240 immutable idx = tc.token.retro.countUntil('.'); 241 assert(idx >= 0); // NONE ends with '.' 242 243 if (tc.test & Test.decode) 244 assert(handler.validate(buf[0..len])); 245 246 assert(buf[0..tc.token.length-idx] == tc.token[0..$-idx]); 247 } 248 else assert(len == -1); 249 } 250 } 251 252 @("JWTAlgorithm.none") 253 @safe unittest 254 { 255 import std.algorithm : filter; 256 import core.memory : GC; 257 258 immutable pre = () @trusted { return GC.stats(); }(); 259 foreach (tc; testCases.filter!(a => a.alg == JWTAlgorithm.none)) 260 { 261 evalTest(NoneHandler.init, tc); 262 } 263 assert((() @trusted { return GC.stats().usedSize; }() - pre.usedSize) == 0); // check for no GC allocations 264 } 265 266 @("AnyAlgValidator") 267 @safe unittest 268 { 269 // valid token 270 enum tok = "eyJhbGciOiJub25lIn0.eyJmb28iOiJiYXIifQ."; 271 enum hdr = `{"alg":"none"}`; 272 enum pay = `{"foo":"bar"}`; 273 char[64] bh, bp; 274 275 // decode header and payload 276 assert(decode(AnyAlgValidator.init, tok, bh[], bp[])); 277 assert(bh[0..hdr.length] == hdr); 278 assert(bp[0..pay.length] == pay); 279 280 // decode payload 281 assert(decode(AnyAlgValidator.init, tok, bp[])); 282 assert(bp[0..pay.length] == pay); 283 284 // just validate 285 assert(validate(AnyAlgValidator.init, tok)); 286 287 // invalid base64 signature 288 assert(!validate(AnyAlgValidator.init, "eyJhbGciOiJub25lIn0.eyJmb28iOiJiYXIifQ.blabla!")); 289 } 290 291 @("Sink tests") 292 @safe unittest 293 { 294 NoneHandler none; 295 enum tok = "eyJhbGciOiJub25lIn0.eyJmb28iOiJiYXIifQ."; 296 enum pay = `{"foo":"bar"}`; 297 enum hdr = `{"alg":"none"}`; 298 299 // char array 300 { 301 char[512] hbuf, pbuf, tbuf; 302 assert(none.decode(tok, hbuf[], pbuf[])); 303 assert(hbuf[0..hdr.length] == hdr); 304 assert(pbuf[0..pay.length] == pay); 305 assert(none.encode(tbuf[], pay) > 0); 306 assert(tbuf[0..tok.length] == tok); 307 } 308 309 // appender 310 { 311 import std.array : Appender; 312 Appender!string hbuf, pbuf, tbuf; 313 assert(none.decode(tok, hbuf, pbuf)); 314 assert(hbuf.data == hdr); 315 assert(pbuf.data == pay); 316 assert(none.encode(tbuf, pay) > 0); 317 assert(tbuf.data == tok); 318 } 319 320 // String from bc.string 321 { 322 import bc.string : String; 323 String hbuf, pbuf, tbuf; 324 assert(none.decode(tok, hbuf, pbuf)); 325 assert(hbuf == hdr); 326 assert(pbuf == pay); 327 assert(none.encode(tbuf, pay) > 0); 328 assert(tbuf == tok); 329 } 330 }