|
1 | 1 | require "../../spec_helper" |
2 | 2 |
|
3 | | -# This spec verifies compatibility with tokens from the l8w8jwt library |
4 | | -# Reference: https://github.com/GlitchedPolygons/l8w8jwt/blob/master/examples/ps256/decode.c |
5 | | -describe "PS256 decode compatibility" do |
6 | | - # Token from l8w8jwt example |
7 | | - jwt_token = "eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InNvbWUta2V5LWlkLWhlcmUtMDEyMzQ1In0.eyJpYXQiOjE1ODAzNDAwMzcsImV4cCI6MTU4MDM0MDYzNywic3ViIjoiR29yZG9uIEZyZWVtYW4iLCJpc3MiOiJCbGFjayBNZXNhIiwiYXVkIjoiQWRtaW5pc3RyYXRvciIsImN0eCI6IlVuZm9yc2VlbiBDb25zZXF1ZW5jZXMiLCJhZ2UiOjI3LCJzaXplIjoxLjg1LCJhbGl2ZSI6dHJ1ZSwibnVsbHRlc3QiOm51bGx9.X4o81UkLLt1mBdoQozWPAtVIRvkX7249fs25FGqrlGzci2exAVQh6g8OzqZlhPO8_VSVGt1bTlurWPhrPwZoeViy1g86MRBLNoiuWEkPg0FFB2jhBPGF2u-cJ2YKd9VSLSjs1fcxSyfG5dKczDo_w3FUL_syNpOpWaWtvByxDn0Cez4SHfTIcaGPKsyYBKhy1t3RgFzm9mCMugRd40omPO4WFKQ1f-boO0ydfvcybEmxMBpT3DsqbKAD9oM0kFWsLMIzOXIp4Uo1J-k3utjieDwaiBu7x2g-bU_0XygnXWIfrSXtUOmntVVFe9am13fIeH-I_3SJlzhLI4QapJ-_s5xeyZ3Y8tHLs-Sqt85Bs_rnewnJpHESXn-G5eK7YTHEvC3luELNrGQlTzQIpTZLYwARikQlhBme-lqvH6hTdGwQy-jhlr41GF5hBKHArFTN0RJBRDKyGgJffDlDDsk3g9NpaZqvOqMvLBHk78TbrQnTKMKY6L7dnAoPcTcl8IgIr9lN37TKFuvAm6nDjcWQUViOO9YtDng3e8cjWaJiizGpTOct-IKn7ZXMzGRrFSmXSOWgeukP5jcwH5dU_0ICDbt2oaid7Bpm1z8EviBGNh0OmjqJ8FmsGst8zaAufpSBwCbV9OCUo84RminY6pW6Lm3BWwIbki-yUOExAWJPjN0" |
8 | | - |
9 | | - # RSA 4096-bit public key from l8w8jwt example |
10 | | - rsa_public_key = "-----BEGIN PUBLIC KEY-----\n" + |
11 | | - "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAoWFe7BbX1nWo5oaSv/Jv\n" + |
12 | | - "IUCWsk/Vi2q8P0cGkefgN5J7MN7Kfv7lq0hl/1cZcJs81IC+GiC+V3aR2zLBNnJJ\n" + |
13 | | - "axa4sqk+hF5DJcD2bF0B80uqPYQUXlQwki/heATnVcke8APuY0kOZykxoD0APAqw\n" + |
14 | | - "0z5KDqgt2vA9G6keM6b9bbL+IvxM+yMk1QV0OQLh6Rkz46DyPSoUFWyXiist47PJ\n" + |
15 | | - "KNyZAfFZx6vEivzBmqRHKe11W9oD/tN5VTQCH/UTSRfyWq/UUMFVMCksLwT6XoWI\n" + |
16 | | - "7F5swgQkSahWkVJ93Qf8cUf1HIZYTMJBYPG4y2NDZ0+ytnH3BNXLMQXg9xbgv6B/\n" + |
17 | | - "iaSVScI4CWIpQTAtNKnJwYg2+RhfYBC07iM56c4a+TjbCWgmd11UYc96dbw83uFR\n" + |
18 | | - "jKZc3+SC38ITCgMuoDPNBlFJK6u8VfYylGEJolGcauVa6yZKwzsJGr5J/LANz+Zy\n" + |
19 | | - "HZmANed+2Hjqxu/H1NGDBdvUGLQbhb/uBJ8oG8iAW5eUyjEJMX0RuncYnBrUjZdE\n" + |
20 | | - "Fr0zJd5VkrfFTd26AjGusbiBevATfj83SNa9uK3N3lSNcLNyNXUjmfOU21NWHAk5\n" + |
21 | | - "QV3TJb6SCTcqWFaYoyKR7H6zxRcArNuIAMW4KhOl4jdNnTxJllC4tr/gkE+uO1nt\n" + |
22 | | - "B9ymLxQBRp8osHjuZpKXr3cCAwEAAQ==\n" + |
23 | | - "-----END PUBLIC KEY-----" |
24 | | - |
25 | | - it "can decode a PS256 token from l8w8jwt library" do |
26 | | - # Decode without verification or validation (since token is expired) |
27 | | - payload, header = JWT.decode(jwt_token, verify: false, validate: false) |
28 | | - |
29 | | - # Verify header |
30 | | - header["alg"].should eq("PS256") |
31 | | - header["typ"].should eq("JWT") |
32 | | - header["kid"].should eq("some-key-id-here-012345") |
33 | | - |
34 | | - # Verify payload claims |
35 | | - payload["iat"].should eq(1580340037) |
36 | | - payload["exp"].should eq(1580340637) |
37 | | - payload["sub"].should eq("Gordon Freeman") |
38 | | - payload["iss"].should eq("Black Mesa") |
39 | | - payload["aud"].should eq("Administrator") |
40 | | - payload["ctx"].should eq("Unforseen Consequences") |
41 | | - payload["age"].should eq(27) |
42 | | - payload["size"].should eq(1.85) |
43 | | - payload["alive"].should eq(true) |
44 | | - payload["nulltest"].as_nil.should be_nil |
45 | | - end |
| 3 | +{% if compare_versions(LibCrypto::OPENSSL_VERSION, "3.0.0") >= 0 %} |
| 4 | + # This spec verifies compatibility with tokens from the l8w8jwt library |
| 5 | + # Reference: https://github.com/GlitchedPolygons/l8w8jwt/blob/master/examples/ps256/decode.c |
| 6 | + describe "PS256 decode compatibility" do |
| 7 | + # Token from l8w8jwt example |
| 8 | + jwt_token = "eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InNvbWUta2V5LWlkLWhlcmUtMDEyMzQ1In0.eyJpYXQiOjE1ODAzNDAwMzcsImV4cCI6MTU4MDM0MDYzNywic3ViIjoiR29yZG9uIEZyZWVtYW4iLCJpc3MiOiJCbGFjayBNZXNhIiwiYXVkIjoiQWRtaW5pc3RyYXRvciIsImN0eCI6IlVuZm9yc2VlbiBDb25zZXF1ZW5jZXMiLCJhZ2UiOjI3LCJzaXplIjoxLjg1LCJhbGl2ZSI6dHJ1ZSwibnVsbHRlc3QiOm51bGx9.X4o81UkLLt1mBdoQozWPAtVIRvkX7249fs25FGqrlGzci2exAVQh6g8OzqZlhPO8_VSVGt1bTlurWPhrPwZoeViy1g86MRBLNoiuWEkPg0FFB2jhBPGF2u-cJ2YKd9VSLSjs1fcxSyfG5dKczDo_w3FUL_syNpOpWaWtvByxDn0Cez4SHfTIcaGPKsyYBKhy1t3RgFzm9mCMugRd40omPO4WFKQ1f-boO0ydfvcybEmxMBpT3DsqbKAD9oM0kFWsLMIzOXIp4Uo1J-k3utjieDwaiBu7x2g-bU_0XygnXWIfrSXtUOmntVVFe9am13fIeH-I_3SJlzhLI4QapJ-_s5xeyZ3Y8tHLs-Sqt85Bs_rnewnJpHESXn-G5eK7YTHEvC3luELNrGQlTzQIpTZLYwARikQlhBme-lqvH6hTdGwQy-jhlr41GF5hBKHArFTN0RJBRDKyGgJffDlDDsk3g9NpaZqvOqMvLBHk78TbrQnTKMKY6L7dnAoPcTcl8IgIr9lN37TKFuvAm6nDjcWQUViOO9YtDng3e8cjWaJiizGpTOct-IKn7ZXMzGRrFSmXSOWgeukP5jcwH5dU_0ICDbt2oaid7Bpm1z8EviBGNh0OmjqJ8FmsGst8zaAufpSBwCbV9OCUo84RminY6pW6Lm3BWwIbki-yUOExAWJPjN0" |
| 9 | + |
| 10 | + # RSA 4096-bit public key from l8w8jwt example |
| 11 | + rsa_public_key = "-----BEGIN PUBLIC KEY-----\n" + |
| 12 | + "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAoWFe7BbX1nWo5oaSv/Jv\n" + |
| 13 | + "IUCWsk/Vi2q8P0cGkefgN5J7MN7Kfv7lq0hl/1cZcJs81IC+GiC+V3aR2zLBNnJJ\n" + |
| 14 | + "axa4sqk+hF5DJcD2bF0B80uqPYQUXlQwki/heATnVcke8APuY0kOZykxoD0APAqw\n" + |
| 15 | + "0z5KDqgt2vA9G6keM6b9bbL+IvxM+yMk1QV0OQLh6Rkz46DyPSoUFWyXiist47PJ\n" + |
| 16 | + "KNyZAfFZx6vEivzBmqRHKe11W9oD/tN5VTQCH/UTSRfyWq/UUMFVMCksLwT6XoWI\n" + |
| 17 | + "7F5swgQkSahWkVJ93Qf8cUf1HIZYTMJBYPG4y2NDZ0+ytnH3BNXLMQXg9xbgv6B/\n" + |
| 18 | + "iaSVScI4CWIpQTAtNKnJwYg2+RhfYBC07iM56c4a+TjbCWgmd11UYc96dbw83uFR\n" + |
| 19 | + "jKZc3+SC38ITCgMuoDPNBlFJK6u8VfYylGEJolGcauVa6yZKwzsJGr5J/LANz+Zy\n" + |
| 20 | + "HZmANed+2Hjqxu/H1NGDBdvUGLQbhb/uBJ8oG8iAW5eUyjEJMX0RuncYnBrUjZdE\n" + |
| 21 | + "Fr0zJd5VkrfFTd26AjGusbiBevATfj83SNa9uK3N3lSNcLNyNXUjmfOU21NWHAk5\n" + |
| 22 | + "QV3TJb6SCTcqWFaYoyKR7H6zxRcArNuIAMW4KhOl4jdNnTxJllC4tr/gkE+uO1nt\n" + |
| 23 | + "B9ymLxQBRp8osHjuZpKXr3cCAwEAAQ==\n" + |
| 24 | + "-----END PUBLIC KEY-----" |
| 25 | + |
| 26 | + it "can decode a PS256 token from l8w8jwt library" do |
| 27 | + # Decode without verification or validation (since token is expired) |
| 28 | + payload, header = JWT.decode(jwt_token, verify: false, validate: false) |
| 29 | + |
| 30 | + # Verify header |
| 31 | + header["alg"].should eq("PS256") |
| 32 | + header["typ"].should eq("JWT") |
| 33 | + header["kid"].should eq("some-key-id-here-012345") |
| 34 | + |
| 35 | + # Verify payload claims |
| 36 | + payload["iat"].should eq(1580340037) |
| 37 | + payload["exp"].should eq(1580340637) |
| 38 | + payload["sub"].should eq("Gordon Freeman") |
| 39 | + payload["iss"].should eq("Black Mesa") |
| 40 | + payload["aud"].should eq("Administrator") |
| 41 | + payload["ctx"].should eq("Unforseen Consequences") |
| 42 | + payload["age"].should eq(27) |
| 43 | + payload["size"].should eq(1.85) |
| 44 | + payload["alive"].should eq(true) |
| 45 | + payload["nulltest"].as_nil.should be_nil |
| 46 | + end |
46 | 47 |
|
47 | | - it "can verify the PS256 signature with the public key" do |
48 | | - # Verify signature (verify: true checks signature, validate: false skips expiry check) |
49 | | - payload, _header = JWT.decode(jwt_token, rsa_public_key, JWT::Algorithm::PS256, verify: true, validate: false) |
| 48 | + it "can verify the PS256 signature with the public key" do |
| 49 | + # Verify signature (verify: true checks signature, validate: false skips expiry check) |
| 50 | + payload, _header = JWT.decode(jwt_token, rsa_public_key, JWT::Algorithm::PS256, verify: true, validate: false) |
50 | 51 |
|
51 | | - # Signature is valid, verify payload |
52 | | - payload["sub"].should eq("Gordon Freeman") |
53 | | - payload["iss"].should eq("Black Mesa") |
54 | | - payload["aud"].should eq("Administrator") |
55 | | - payload["ctx"].should eq("Unforseen Consequences") |
56 | | - end |
| 52 | + # Signature is valid, verify payload |
| 53 | + payload["sub"].should eq("Gordon Freeman") |
| 54 | + payload["iss"].should eq("Black Mesa") |
| 55 | + payload["aud"].should eq("Administrator") |
| 56 | + payload["ctx"].should eq("Unforseen Consequences") |
| 57 | + end |
57 | 58 |
|
58 | | - it "distinguishes between signature verification and claim validation" do |
59 | | - # verify: true, validate: false -> checks signature but not expiry |
60 | | - payload, _ = JWT.decode(jwt_token, rsa_public_key, JWT::Algorithm::PS256, verify: true, validate: false) |
61 | | - payload["sub"].should eq("Gordon Freeman") |
| 59 | + it "distinguishes between signature verification and claim validation" do |
| 60 | + # verify: true, validate: false -> checks signature but not expiry |
| 61 | + payload, _ = JWT.decode(jwt_token, rsa_public_key, JWT::Algorithm::PS256, verify: true, validate: false) |
| 62 | + payload["sub"].should eq("Gordon Freeman") |
62 | 63 |
|
63 | | - # verify: false, validate: false -> no checks at all |
64 | | - payload, _ = JWT.decode(jwt_token, verify: false, validate: false) |
65 | | - payload["sub"].should eq("Gordon Freeman") |
| 64 | + # verify: false, validate: false -> no checks at all |
| 65 | + payload, _ = JWT.decode(jwt_token, verify: false, validate: false) |
| 66 | + payload["sub"].should eq("Gordon Freeman") |
66 | 67 |
|
67 | | - # verify: true, validate: true -> checks signature AND expiry (will fail) |
68 | | - expect_raises(JWT::ExpiredSignatureError) do |
69 | | - JWT.decode(jwt_token, rsa_public_key, JWT::Algorithm::PS256, verify: true, validate: true) |
| 68 | + # verify: true, validate: true -> checks signature AND expiry (will fail) |
| 69 | + expect_raises(JWT::ExpiredSignatureError) do |
| 70 | + JWT.decode(jwt_token, rsa_public_key, JWT::Algorithm::PS256, verify: true, validate: true) |
| 71 | + end |
70 | 72 | end |
71 | | - end |
72 | 73 |
|
73 | | - # Note: Validation tests (iss, sub, aud) are not included here because |
74 | | - # the token is expired and validation checks exp first. The existing |
75 | | - # claim validation tests in other specs cover those features. |
| 74 | + # Note: Validation tests (iss, sub, aud) are not included here because |
| 75 | + # the token is expired and validation checks exp first. The existing |
| 76 | + # claim validation tests in other specs cover those features. |
76 | 77 |
|
77 | | - it "properly decodes all data types in payload" do |
78 | | - payload, _ = JWT.decode(jwt_token, verify: false, validate: false) |
| 78 | + it "properly decodes all data types in payload" do |
| 79 | + payload, _ = JWT.decode(jwt_token, verify: false, validate: false) |
79 | 80 |
|
80 | | - # Integer |
81 | | - payload["age"].as_i.should eq(27) |
| 81 | + # Integer |
| 82 | + payload["age"].as_i.should eq(27) |
82 | 83 |
|
83 | | - # Float |
84 | | - payload["size"].as_f.should eq(1.85) |
| 84 | + # Float |
| 85 | + payload["size"].as_f.should eq(1.85) |
85 | 86 |
|
86 | | - # Boolean |
87 | | - payload["alive"].as_bool.should eq(true) |
| 87 | + # Boolean |
| 88 | + payload["alive"].as_bool.should eq(true) |
88 | 89 |
|
89 | | - # Null (JSON null is represented as JSON::Any, use .as_nil to extract) |
90 | | - payload["nulltest"].as_nil.should be_nil |
| 90 | + # Null (JSON null is represented as JSON::Any, use .as_nil to extract) |
| 91 | + payload["nulltest"].as_nil.should be_nil |
91 | 92 |
|
92 | | - # Strings |
93 | | - payload["sub"].as_s.should eq("Gordon Freeman") |
94 | | - payload["ctx"].as_s.should eq("Unforseen Consequences") |
95 | | - end |
| 93 | + # Strings |
| 94 | + payload["sub"].as_s.should eq("Gordon Freeman") |
| 95 | + payload["ctx"].as_s.should eq("Unforseen Consequences") |
| 96 | + end |
96 | 97 |
|
97 | | - it "token is expired and should fail exp validation" do |
98 | | - # Token exp is 1580340637 (2020-01-29 23:30:37 UTC), which has passed |
99 | | - expect_raises(JWT::ExpiredSignatureError, "Signature is expired") do |
100 | | - JWT.decode(jwt_token, rsa_public_key, JWT::Algorithm::PS256) |
| 98 | + it "token is expired and should fail exp validation" do |
| 99 | + # Token exp is 1580340637 (2020-01-29 23:30:37 UTC), which has passed |
| 100 | + expect_raises(JWT::ExpiredSignatureError, "Signature is expired") do |
| 101 | + JWT.decode(jwt_token, rsa_public_key, JWT::Algorithm::PS256) |
| 102 | + end |
101 | 103 | end |
102 | 104 | end |
103 | | -end |
| 105 | +{% end %} |
0 commit comments