Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/doc/
/lib/
/libs/
/.crystal/
/.shards/
Expand All @@ -7,4 +8,3 @@
# Libraries don't need dependency lock
# Dependencies will be locked in application that uses them
/shard.lock

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ payload, header = JWT.decode(token, "$secretKey", "HS256")
## Supported algorithms
* [x] none
* [x] HMAC (HS256, HS384, HS512)
* [ ] RSA - in progress
* [x] RSA (RS256, RS384, RS512)

## Supported reserved claim names
JSON Web Token defines some reserved claim names and how they should be used.
Expand Down
4 changes: 4 additions & 0 deletions shard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,8 @@ version: 0.4.0
authors:
- Potapov Sergey <blake131313@gmail.com>

dependencies:
openssl_ext:
github: stakach/openssl_ext

license: MIT
64 changes: 64 additions & 0 deletions spec/integration/algorithms/rsa_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
require "../../spec_helper"

describe JWT do
secret_key = "-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQC5+5+xnWggxNnnmCSNbIwTQFjcyawcvmPupeXs10sfhUAHUxtm
T5zH3AI46JrRZN7KV5Ac5bQWzF9ZMPeHqmq5FBdYooIF8W7lVtYx23OQX5vjFRN0
LRY8hyOKL07Us+aUeMwDXX7M6o58XO4bqOh8pGOqFLscCAkdAP9lDgeDGwIDAQAB
AoGAcRt/jnSNbEhrwXZ83GmkctzSbkxUWRLNEclhIP36WQwf2ZSIeFt4nO/Hhjao
WSqAeAxyv7BPKwJWBpdKIv7Ycfbu2c1JxWgacuotewMk5IYPXUs89QY3AL5I4BJd
Zqd3o9K4OWwakukkfjxHKFC/grifNa4yVQ6IZn+XuW/AspkCQQDlmzkUapzg0n0t
3gmK6KQD9f5YdXKYGYzYO3Scrtrz53fewqfDXdLC7TGL9qw9vGEFvSE727vwR3X+
+DZ6RWYvAkEAz1yqUNnrPwzGx3JuINIXgfzGTq4gSf+xRjb5qDJUPnMt4I3PrPyV
pm34aUCgo26go2+itBGjzFDaJCOT4izi1QJAJq6E6kSf01yCzFRo5ScWYrhxtjNr
L+a2DMPPfIoUxxyK3FOM8eP/mulc/Ih9MhVnfxEC5VO6kNtpLKBihSzl7wJBAJrR
4eu5uJV7kZJqEmV41spbkyg9g6gcOxxkgWQeJ5302wT0fGD4uTbolnbnJMjBGTjN
adot7XDn0Ob4lTpiLv0CQQDkECppYQ4N0ecegg1xPVqf19fHo/WGHGuScjfUPTI/
k0LaJjYM2ycehinmuLHgY3qdDJgtEbt4WG5XNQzhyfaN
-----END RSA PRIVATE KEY-----"

wrong_key = OpenSSL::RSA.new(1024).to_pem
payload = {"foo" => "bar"}

algorithms = [
["RS256", "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.rJUpucYWdjmGiVGHrU4TMwfYNcF52Hm1Q4hJfHhfUPvVL-S0fRHRgwNns90MDOFReXH8_6swbtezzeuQleSY-NdYLEvnXwYHzjLP-Bxc3mrKNMnf8ta1lYB7NqdnIu2nqcNjflJBubn5sIi7-zZew_ohqgMP8H7ptDuICr7ibGQ"],
["RS384", "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzM4NCJ9.eyJmb28iOiJiYXIifQ.FfdS8chkIE-PRU61h8VLZgVYvKI3yAvaEpGjqDP0ypGa_0rF6iOCkRuEByhBsH-lCVmKcU-1bp3OsEGXtuYlthpklM76gDDP4YMss2mdH4_xr6P9UQ7lL_xb8inOCbnNMsm7xecIPElDkJ5W22iwF2fbi67p9hlJwgcfBsyfqX4"],
["RS512", "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzUxMiJ9.eyJmb28iOiJiYXIifQ.StG6Du1SpGrP7BdFyW6VjMwHudEdekdlJjbT1ByWFPerp7hZ1P7ukHOFMzVVOm6e0xLO6XGk11jDvC_zG2wunjEoMKYY_DuSmUOjVcZVz5m5korH9PJNJRREoQPa42QTVUaMeuv8A3xlq6_SG9wLCGVib4JsIFyS1qPzS3PlNZg"],
]

algorithms.each do |alg_data|
alg, expected_token = alg_data

describe "algorithm #{alg}" do
it "generates proper token, that can be decoded" do
token = JWT.encode(payload, secret_key, alg)
token.should eq(expected_token)

decoded_token = JWT.decode(token, secret_key, alg)
decoded_token[0].should eq(payload)
decoded_token[1].should eq({"typ" => "JWT", "alg" => alg})
end

describe "#decode" do
context "when token was signed with another key" do
it "raises JWT::VerificationError" do
token = JWT.encode(payload, wrong_key, alg)
expect_raises(JWT::VerificationError, "Signature verification failed") do
JWT.decode(token, secret_key, alg)
end
end
end

context "when token contains not 3 segments" do
it "raises JWT::DecodeError" do
["e30", "e30.e30", "e30.e30.e30.e30"].each do |invalid_token|
expect_raises(JWT::DecodeError) do
JWT.decode(invalid_token, secret_key, alg)
end
end
end
end
end
end
end
end
24 changes: 18 additions & 6 deletions src/jwt.cr
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
require "json"
require "base64"
require "openssl/hmac"
require "openssl_ext"

require "./jwt/*"

module JWT
extend self

def encode(payload, key : String, algorithm : String) : String
def encode(payload, key : String, algorithm : String, **header_keys) : String
segments = [] of String
segments << encode_header(algorithm)
segments << encode_header(algorithm, **header_keys)
segments << encode_payload(payload)
segments << encoded_signature(algorithm, key, segments.join("."))
segments.join(".")
Expand Down Expand Up @@ -48,10 +49,9 @@ module JWT
raise DecodeError.new("Invalid JSON")
end

def encode_header(algorithm : String) : String
header = {"typ" => "JWT", "alg" => algorithm}
json = header.to_json
base64_encode(json)
def encode_header(algorithm : String, **keys) : String
header = {typ: "JWT", alg: algorithm}.merge(keys)
base64_encode(header.to_json)
end

def encode_payload(payload) : String
Expand All @@ -73,6 +73,18 @@ module JWT
OpenSSL::HMAC.digest(:sha384, key, data)
when "HS512"
OpenSSL::HMAC.digest(:sha512, key, data)
when "RS256"
rsa = OpenSSL::RSA.new(key)
digest = OpenSSL::Digest.new("sha256")
rsa.sign(digest, data)
when "RS384"
rsa = OpenSSL::RSA.new(key)
digest = OpenSSL::Digest.new("sha384")
rsa.sign(digest, data)
when "RS512"
rsa = OpenSSL::RSA.new(key)
digest = OpenSSL::Digest.new("sha512")
rsa.sign(digest, data)
else raise(UnsupportedAlogrithmError.new("Unsupported algorithm: #{algorithm}"))
end
end
Expand Down