diff --git a/.rspec b/.rspec new file mode 100644 index 00000000..5d2ee7d9 --- /dev/null +++ b/.rspec @@ -0,0 +1,2 @@ +--color +--format d \ No newline at end of file diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 00000000..c4410459 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,2 @@ +Metrics/LineLength: + Enabled: false diff --git a/.travis.yml b/.travis.yml index b374a121..70c71e6d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,6 @@ sudo: false cache: bundler language: ruby rvm: - - 1.9.2 - 1.9.3 - 2.0.0 - 2.1.0 diff --git a/Rakefile b/Rakefile index 5e5da513..09842b6a 100644 --- a/Rakefile +++ b/Rakefile @@ -4,13 +4,13 @@ require 'rake' require 'echoe' Echoe.new('jwt', '1.5.1') do |p| - p.description = 'JSON Web Token implementation in Ruby' - p.url = 'http://github.com/progrium/ruby-jwt' - p.author = 'Jeff Lindsay' - p.email = 'progrium@gmail.com' + p.description = 'JSON Web Token implementation in Ruby' + p.url = 'http://github.com/progrium/ruby-jwt' + p.author = 'Jeff Lindsay' + p.email = 'progrium@gmail.com' p.ignore_pattern = ['tmp/*'] p.development_dependencies = ['echoe >=4.6.3'] - p.licenses = 'MIT' + p.licenses = 'MIT' end task :test do diff --git a/lib/jwt.rb b/lib/jwt.rb index 1b674742..ab5474b3 100644 --- a/lib/jwt.rb +++ b/lib/jwt.rb @@ -203,7 +203,7 @@ def verify_signature(algo, key, signing_input, signature) fail JWT::VerificationError, 'Algorithm not supported' end rescue OpenSSL::PKey::PKeyError - fail JWT::VerificationError, 'Signature verification raised' + raise JWT::VerificationError, 'Signature verification raised' ensure OpenSSL.errors.clear end diff --git a/lib/jwt/json.rb b/lib/jwt/json.rb index 48e30da6..a1cc8a54 100644 --- a/lib/jwt/json.rb +++ b/lib/jwt/json.rb @@ -8,7 +8,7 @@ module Json def decode_json(encoded) JSON.parse(encoded) rescue JSON::ParserError - raise JWT::DecodeError.new('Invalid segment encoding') + raise JWT::DecodeError, 'Invalid segment encoding' end def encode_json(raw) @@ -21,7 +21,7 @@ def encode_json(raw) def decode_json(encoded) MultiJson.decode(encoded) rescue MultiJson::LoadError - raise JWT::DecodeError.new('Invalid segment encoding') + raise JWT::DecodeError, 'Invalid segment encoding' end def encode_json(raw) diff --git a/spec/fixtures/certs/ec256-private.pem b/spec/fixtures/certs/ec256-private.pem new file mode 100644 index 00000000..40c7a8f6 --- /dev/null +++ b/spec/fixtures/certs/ec256-private.pem @@ -0,0 +1,8 @@ +-----BEGIN EC PARAMETERS----- +BggqhkjOPQMBBw== +-----END EC PARAMETERS----- +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIJmVse5uPfj6B4TcXrUAvf9/8pJh+KrKKYLNcmOnp/vPoAoGCCqGSM49 +AwEHoUQDQgAEAr+WbDE5VtIDGhtYMxvEc6cMsDBc/DX1wuhIMu8dQzOLSt0tpqK9 +MVfXbVfrKdayVFgoWzs8MilcYq0QIhKx/w== +-----END EC PRIVATE KEY----- diff --git a/spec/fixtures/certs/ec256-public.pem b/spec/fixtures/certs/ec256-public.pem new file mode 100644 index 00000000..e6f22827 --- /dev/null +++ b/spec/fixtures/certs/ec256-public.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAr+WbDE5VtIDGhtYMxvEc6cMsDBc +/DX1wuhIMu8dQzOLSt0tpqK9MVfXbVfrKdayVFgoWzs8MilcYq0QIhKx/w== +-----END PUBLIC KEY----- diff --git a/spec/fixtures/certs/ec256-wrong-private.pem b/spec/fixtures/certs/ec256-wrong-private.pem new file mode 100644 index 00000000..260df0b6 --- /dev/null +++ b/spec/fixtures/certs/ec256-wrong-private.pem @@ -0,0 +1,8 @@ +-----BEGIN EC PARAMETERS----- +BgUrgQQACg== +-----END EC PARAMETERS----- +-----BEGIN EC PRIVATE KEY----- +MHQCAQEEICfA4AaomONdmPTzeyrx5U/jugYXTERyb5U3ETTv7Hx7oAcGBSuBBAAK +oUQDQgAEPmuXZT3jpJnEMVPOW6RMsmxeGLOCE1PN6fwvUwOsxv7YnyoQ5/bpo64n ++Jp4slSl1aUNoCBF2oz9bS0iyBo3jg== +-----END EC PRIVATE KEY----- diff --git a/spec/fixtures/certs/ec256-wrong-public.pem b/spec/fixtures/certs/ec256-wrong-public.pem new file mode 100644 index 00000000..511a0def --- /dev/null +++ b/spec/fixtures/certs/ec256-wrong-public.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEPmuXZT3jpJnEMVPOW6RMsmxeGLOCE1PN +6fwvUwOsxv7YnyoQ5/bpo64n+Jp4slSl1aUNoCBF2oz9bS0iyBo3jg== +-----END PUBLIC KEY----- diff --git a/spec/fixtures/certs/ec384-private.pem b/spec/fixtures/certs/ec384-private.pem new file mode 100644 index 00000000..553f27c1 --- /dev/null +++ b/spec/fixtures/certs/ec384-private.pem @@ -0,0 +1,9 @@ +-----BEGIN EC PARAMETERS----- +BgUrgQQAIg== +-----END EC PARAMETERS----- +-----BEGIN EC PRIVATE KEY----- +MIGkAgEBBDDxOljqUKw9YNhkluSJIBAYO1YXcNtS+vckd5hpTZ5toxsOlwbmyrnU +Tn+D5Xma1m2gBwYFK4EEACKhZANiAASQwYTiRvXu1hMHceSosMs/8uf50sJI3jvK +kdSkvuRAPxSzhtrUvCQDnVsThFq4aOdZZY1qh2ErJGtzmrx+pEsJvJnvfOTG3NGU +KRalek+LQfVqAUSvDMKlxdkz2e67tso= +-----END EC PRIVATE KEY----- diff --git a/spec/fixtures/certs/ec384-public.pem b/spec/fixtures/certs/ec384-public.pem new file mode 100644 index 00000000..6b7638d4 --- /dev/null +++ b/spec/fixtures/certs/ec384-public.pem @@ -0,0 +1,5 @@ +-----BEGIN PUBLIC KEY----- +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEkMGE4kb17tYTB3HkqLDLP/Ln+dLCSN47 +ypHUpL7kQD8Us4ba1LwkA51bE4RauGjnWWWNaodhKyRrc5q8fqRLCbyZ73zkxtzR +lCkWpXpPi0H1agFErwzCpcXZM9nuu7bK +-----END PUBLIC KEY----- diff --git a/spec/fixtures/certs/ec384-wrong-private.pem b/spec/fixtures/certs/ec384-wrong-private.pem new file mode 100644 index 00000000..1cc5f0d8 --- /dev/null +++ b/spec/fixtures/certs/ec384-wrong-private.pem @@ -0,0 +1,9 @@ +-----BEGIN EC PARAMETERS----- +BgUrgQQAIg== +-----END EC PARAMETERS----- +-----BEGIN EC PRIVATE KEY----- +MIGkAgEBBDAfZW47dSKnC5JkSVOk1ERxCIi/IJ1p1WBnVGx4hnrNHy+dxtaZJaF+ +YLInFQ/QbYegBwYFK4EEACKhZANiAAQwXkx4BFBGLXbzl5yVrfxK7er8hSi38iDE +K2+7cdrR137Wn5JUnL4WTwXTzkyUgfBOL3sHNozwfgU03GD/EOUEKqzsIJiz2cbP +bFALd4hS+8T4szDLVC9Jl1W6k0CAtmM= +-----END EC PRIVATE KEY----- diff --git a/spec/fixtures/certs/ec384-wrong-public.pem b/spec/fixtures/certs/ec384-wrong-public.pem new file mode 100644 index 00000000..693bd019 --- /dev/null +++ b/spec/fixtures/certs/ec384-wrong-public.pem @@ -0,0 +1,5 @@ +-----BEGIN PUBLIC KEY----- +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEMF5MeARQRi1285ecla38Su3q/IUot/Ig +xCtvu3Ha0dd+1p+SVJy+Fk8F085MlIHwTi97BzaM8H4FNNxg/xDlBCqs7CCYs9nG +z2xQC3eIUvvE+LMwy1QvSZdVupNAgLZj +-----END PUBLIC KEY----- diff --git a/spec/fixtures/certs/ec512-private.pem b/spec/fixtures/certs/ec512-private.pem new file mode 100644 index 00000000..fddd8889 --- /dev/null +++ b/spec/fixtures/certs/ec512-private.pem @@ -0,0 +1,10 @@ +-----BEGIN EC PARAMETERS----- +BgUrgQQAIw== +-----END EC PARAMETERS----- +-----BEGIN EC PRIVATE KEY----- +MIHcAgEBBEIB0/+ffxEj7j62xvGaB5pvzk888e412ESO/EK/K0QlS9dSF8+Rj1rG +zqpRB8fvDnoe8xdmkW/W5GKzojMyv7YQYumgBwYFK4EEACOhgYkDgYYABAEw74Yw +aTbPY6TtWmxx6LJDzCX2nKWCPnKdZcEH9Ncu8g5RjRBRq2yacja3OoS6nA2YeDng +reBJxZr376P6Ns6XcQFWDA6K/MCTrEBCsPxXZNxd8KR9vMGWhgNtWRrcKzwJfQkr +suyehZkbbYyFnAWyARKHZuV7VUXmeEmRS/f93MPqVA== +-----END EC PRIVATE KEY----- diff --git a/spec/fixtures/certs/ec512-public.pem b/spec/fixtures/certs/ec512-public.pem new file mode 100644 index 00000000..a74a57b0 --- /dev/null +++ b/spec/fixtures/certs/ec512-public.pem @@ -0,0 +1,6 @@ +-----BEGIN PUBLIC KEY----- +MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBMO+GMGk2z2Ok7VpsceiyQ8wl9pyl +gj5ynWXBB/TXLvIOUY0QUatsmnI2tzqEupwNmHg54K3gScWa9++j+jbOl3EBVgwO +ivzAk6xAQrD8V2TcXfCkfbzBloYDbVka3Cs8CX0JK7LsnoWZG22MhZwFsgESh2bl +e1VF5nhJkUv3/dzD6lQ= +-----END PUBLIC KEY----- diff --git a/spec/fixtures/certs/ec512-wrong-private.pem b/spec/fixtures/certs/ec512-wrong-private.pem new file mode 100644 index 00000000..b6ec7b73 --- /dev/null +++ b/spec/fixtures/certs/ec512-wrong-private.pem @@ -0,0 +1,10 @@ +-----BEGIN EC PARAMETERS----- +BgUrgQQAIw== +-----END EC PARAMETERS----- +-----BEGIN EC PRIVATE KEY----- +MIHbAgEBBEG/KbA2oCbiCT6L3V8XSz2WKBy0XhGvIFbl/ZkXIXnkYt+1B7wViSVo +KCHuMFsi6xU/5nE1EuDG2UsQJmKeAMkIOKAHBgUrgQQAI6GBiQOBhgAEAG0TFWe5 +cZ5DZIyfuysrCoQySTNxd+aT8sPIxsx7mW6YBTsuO6rEgxyegd2Auy4xtikxpzKv +soMXR02999Aaus2jAAt/wxrhhr41BDP4MV0b6Zngb72hna0pcGqit5OyU8AbOJUZ ++rdyowRGsOY+aPbOyVhdNcsEdxYC8GdIyCQLBC1H +-----END EC PRIVATE KEY----- diff --git a/spec/fixtures/certs/ec512-wrong-public.pem b/spec/fixtures/certs/ec512-wrong-public.pem new file mode 100644 index 00000000..a84be38c --- /dev/null +++ b/spec/fixtures/certs/ec512-wrong-public.pem @@ -0,0 +1,6 @@ +-----BEGIN PUBLIC KEY----- +MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAbRMVZ7lxnkNkjJ+7KysKhDJJM3F3 +5pPyw8jGzHuZbpgFOy47qsSDHJ6B3YC7LjG2KTGnMq+ygxdHTb330Bq6zaMAC3/D +GuGGvjUEM/gxXRvpmeBvvaGdrSlwaqK3k7JTwBs4lRn6t3KjBEaw5j5o9s7JWF01 +ywR3FgLwZ0jIJAsELUc= +-----END PUBLIC KEY----- diff --git a/spec/fixtures/certs/rsa-1024-private.pem b/spec/fixtures/certs/rsa-1024-private.pem new file mode 100644 index 00000000..7f01c002 --- /dev/null +++ b/spec/fixtures/certs/rsa-1024-private.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXgIBAAKBgQDO/ahgFDvniFoQ1dm+MdnkBi+Ts5W9AtQNgw4ZHIdPnqEzSgW7 +0opKEu8hnlLqsIyU2BC2op/xOanipdbXObuFlA6bth1cYRI+YJlR3BbPGOIL6YbJ +ud9m0gIsBlCDLm4e/E45ZS+emudISP7/SF7zxvxZlnr1z7HTm7nIIVBvuQIDAQAB +AoGBAMzFQAccvU6GI6O4C5sOsiHUxMh3xtCftaxQVGgfQvVPVuXoeteep1Q0ewFl +IV4vnkO5pH8pTtVTWG9x5KIy6QCql4qvr2jkOm4mo9uogrpNklvBl2lN4Lxubj0N +mGRXaM3hckZl8+JT6uzfBfjy+pd8AOigJGPQCOZn4gmANW7pAkEA82Nh4wpj6ZRU +NBiBq3ONZuH4xJm59MI2FWRJsGUFUYdSaFwyKKim52/13d8iUb7v9utWQFRatCXz +Lqw9fQyVrwJBANm3dBOVxpUPrYEQsG0q2rdP+u6U3woylxwtQgJxImZKZmmJlPr8 +9v23rhydvCe1ERPYe7EjF4RGWVPN3KLdExcCQDdzNfL3BApMS97OkoRQQC/nXbjU +2SPlN1MqVQuGCG8pqGG0V40h11y1CkvxMS10ldEojq77SOrwFnZUsXGS82sCQQC6 +XdO7QCaxSq5XIRYlHN4EtS40NLOIYy3/LK6osHel4GIyTVd+UjSLk0QzssJxqwln +V5TqWQO0cxPcLQiFUYEZAkEA2G84ilb9QXOgbNyoE1VifNk49hhodbSskLb86uwY +Vgtzq1ZsqoPBCasr4WRiXt270n+mo5dNYRlZwiUn9lH78Q== +-----END RSA PRIVATE KEY----- diff --git a/spec/fixtures/certs/rsa-1024-public.pem b/spec/fixtures/certs/rsa-1024-public.pem new file mode 100644 index 00000000..7bec6ef0 --- /dev/null +++ b/spec/fixtures/certs/rsa-1024-public.pem @@ -0,0 +1,6 @@ +-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDO/ahgFDvniFoQ1dm+MdnkBi+T +s5W9AtQNgw4ZHIdPnqEzSgW70opKEu8hnlLqsIyU2BC2op/xOanipdbXObuFlA6b +th1cYRI+YJlR3BbPGOIL6YbJud9m0gIsBlCDLm4e/E45ZS+emudISP7/SF7zxvxZ +lnr1z7HTm7nIIVBvuQIDAQAB +-----END PUBLIC KEY----- diff --git a/spec/fixtures/certs/rsa-2048-private.pem b/spec/fixtures/certs/rsa-2048-private.pem new file mode 100644 index 00000000..dab50df2 --- /dev/null +++ b/spec/fixtures/certs/rsa-2048-private.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA4GzZTLU48c4WbyvHi+QKrB71x+T0eq5hqDbQqnlYjhD1Ika7 +io1iplsdJWJuyxfYbUkb2Ol0fj4koZ/GS6lgCZr4+8UHbr1qf0Eu5HZSpszs2YxY +8U5RHnrpw67co7hlgAR9HbyNf5XIYgLV9ldHH/eazwnc3F/hgNsV0xjScVilejgo +cJ4zcsyymvW8t42lteM7bI867ZuJhGop/V+Y0HFyrMsPoQyLuCUpr6ulOfrkr7ZO +dhAIG8r1HcjOp/AUjM15vfXcbUZjkM/VloifX1YitU3upMGJ8/DpFGffMOImrn5r +6BT494V8rRyN2qvQoAkLJpqZ0avLxwiR2lgVQQIDAQABAoIBAEH0Ozgr2fxWEInD +V/VooypKPvjr9F1JejGxSkmPN9MocKIOH3dsbZ1uEXa3ItBUxan4XlK06SNgp+tH +xULfF/Y6sQlsse59hBq50Uoa69dRShn1AP6JgZVvkduMPBNxUYL5zrs6emsQXb9Q +DglDRQfEAJ7vyxSIqQDxYcyT8uSUF70dqFe+E9B2VE3D6ccHc98k41pJrAFAUFH1 +wwvDhfyYr7/Ultut9wzpZvU1meF3Vna3GOUHfxrG6wu1G+WIWHGjouzThsc1qiVI +BtMCJxuCt5fOXRbU4STbMqhB6sZHiOh6J/dZU6JwRYt+IS8FB6kCNFSEWZWQledJ +XqtYSQECgYEA9nmnFTRj3fTBq9zMXfCRujkSy6X2bOb39ftNXzHFuc+I6xmv/3Bs +P9tDdjueP/SnCb7i/9hXkpEIcxjrjiqgcvD2ym1hE4q+odMzRAXYMdnmzI34SVZE +U5hYJcYsXNKrTTleba7QgqdORmyJ9FwqLO40udvmrZMY223XDwgRkOkCgYEA6RkO +5wjjrWWp/G1YN3KXZTS1m2/eGrUThohXKAfAjbWWiouNLW2msXrxEWsPRL6xKiHu +X9cwZwzi3MstAgk+bphUGUVUkGKNDjWHJA25tDYjbPtkd6xbL4eCHsKpNL3HNYr9 +N0CIvgn7qjaHRBem0iK7T6keY4axaSVddEwYapkCgYEA13K5qaB1F4Smcpt8DTWH +vPe8xUUaZlFzOJLmLCsuwmB2N8Ppg2j7RspcaxJsH021YaB5ftjWm+ipMSr8ZPY/ +8JlPsNzxuYpTXtNmAbT2KYVm6THEch61dTk6/DIBf1YrpUJbl5by7vJeStL/uBmE +SGgksL5XIyzs0opuLdaIvFkCgYAyBLWE8AxjFfCvAQuwAj/ocLITo6KmWnrRIIqL +RXaVMgUWv7FQsTnW1cnK8g05tC2yG8vZ9wQk6Mf5lwOWb0NdWgSZ0528ydj41pWk +L+nMeN2LMjqxz2NVxJ8wWJcUgTCxFZ0WcRumo9/D+6V1ABpE9zz4cBLcSnfhVypB +nV6T6QKBgQCSZNCQ9HPxjAgYcsqc5sjNwuN1GHQZSav3Tye3k6zHENe1lsteT9K8 +xciGIuhybKZBvB4yImIIHCtnH+AS+mHAGqHarjNDMfvjOq0dMibPx4+bkIiHdBIH +Xz+j5kmntvFiUnzr0Z/Tcqo+r8FvyCo1YWgwqGP8XoFrswD7gy7cZw== +-----END RSA PRIVATE KEY----- diff --git a/spec/fixtures/certs/rsa-2048-public.pem b/spec/fixtures/certs/rsa-2048-public.pem new file mode 100644 index 00000000..c80a9523 --- /dev/null +++ b/spec/fixtures/certs/rsa-2048-public.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4GzZTLU48c4WbyvHi+QK +rB71x+T0eq5hqDbQqnlYjhD1Ika7io1iplsdJWJuyxfYbUkb2Ol0fj4koZ/GS6lg +CZr4+8UHbr1qf0Eu5HZSpszs2YxY8U5RHnrpw67co7hlgAR9HbyNf5XIYgLV9ldH +H/eazwnc3F/hgNsV0xjScVilejgocJ4zcsyymvW8t42lteM7bI867ZuJhGop/V+Y +0HFyrMsPoQyLuCUpr6ulOfrkr7ZOdhAIG8r1HcjOp/AUjM15vfXcbUZjkM/Vloif +X1YitU3upMGJ8/DpFGffMOImrn5r6BT494V8rRyN2qvQoAkLJpqZ0avLxwiR2lgV +QQIDAQAB +-----END PUBLIC KEY----- diff --git a/spec/fixtures/certs/rsa-2048-wrong-private.pem b/spec/fixtures/certs/rsa-2048-wrong-private.pem new file mode 100644 index 00000000..7cae44c6 --- /dev/null +++ b/spec/fixtures/certs/rsa-2048-wrong-private.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAzHAVGaW9j4l3/b4ngcjjoIoIcnsQEWOMqErb5VhLZMGIq1gE +O5qxPDAwooKsNotzcAOB3ZyLn7p5D+dmOrNUYkYWgYITNGeSifrnVqQugd5Fh1L8 +K7zOGltUo2UtjbN4uJ56tzxBMZp2wejs2/Qu0eu0xZK3To+YkDcWOk92rmNgmUSQ +C/kNyIOj+yBvOo3wTk6HvbhoIarCgJ6Lay1v/hMLyQLzwRY/Qfty1FTIDyTv2dch +47FsfkZ1KAL+MbUnHuCBPzGxRjXa8Iy9Z7YGxrYasUt1b0um64bscxoIiCu8yLL8 +jlg01Rwrjr/MTwKRhwXlMp8B7HTonwtaG6arJwIDAQABAoIBAGFR4dmJusl/qW1T +fj8cQLAFxaupxaZhe24J5NAyzgEy2Dqo9ariIwkB78UM66ozjEqAgOvcP+NTw5m8 +kD/VapA1yTTxlO7XdzzUAhiOo80S4IphCMZRZNPLMmluGtdf3lIUr1pXBrn0TCBX +H5o9jaREzpNXGof9d6T/dEdh2J9+uE/p1xE5GSxQfaPheZzCG7636La/DcArg/UR ++TusPqp62BEmk96pE/KKJRmEeH+WnPfSh6sMpLxi3hkEU7AynpliGT6Z6xV4csBI +S/rdpkcj5DWpbnQzkwdrnL2Q+POEq/vlx5/NlezvtQPNLvQWDyY4yBCoMKGb3EbX +xrxP7MECgYEA/kwe4P0Mqk+087IyhjDBGPfcMt8gfYc9nzNfIYSWdSwuSag/hqHq +I4GwHQzUV9ix3iM6w5hin10yAzWxCYZg9hquV+lSvNNpGB76FX6oOqwuAhyQMRwv +eW+VUyfFXeJugwL5JuIaNTvwPpQVDHYtELLifie+uzJ5HC6dhg/XchcCgYEAzc5/ ++IXjOlExd/mBgFk/5Y87ifA0ZOgbaJXifYgU0aNSgz1piHxU3n2p4jJ9lSdwwCl2 +Fb5EN7666t20PL5QcXJ5ZdaTRLzRlYiqTWzfYHBgttbB1Jl3Ed9GsKuzRgaRqGFC +ANJSqZlKG0NZ3keRtuKdFwq+IVOnsQr9g0TZiXECgYEAqUgtCiMKCloTIGMQpSnR +cXiWWjsUmturls4Q1vQ3YHrvuVLKLyqb/dT4Uu5WcMAs765OESThCit0/pQAbVHK +PCpYwubskAzAGjGM00BEZwJ1gixXhIm5xMIWCowgI7Z3ULlq+IptXeCvtkjHlksZ +BtO+WLLGkkEwRCV38WWcSzMCgYA/Xxqgl/mD94RYAQgTUWgPc69Nph08BQyLg7ue +E8z1UGkT6FEaqc4oRGGPOSTaTK63PQ0TXOb8k0pTD7l0CtYSWMFwzkXCoLGYbeCi +vqd5tqDRLAe7QxYa9rl5pSUqptMrGeeNATZa6sya4H5Hp5oCyny8n54z/OJh7ZRq +W0TwwQKBgQDDP7ksm2pcqadaVAmODdOlaDHbaEcxp8wN7YVz0lM3UpJth96ukbj7 +S39eJhXYWOn6oJQb/lN9fGOYqjg3y6IchGZDp67ATvWYvn/NY0R7mt4K4oHx5TuN +rSQlP3WmOGv8Kemw892uRfW/jZyBEHhsfS213WDttVPn9F635GdNWw== +-----END RSA PRIVATE KEY----- diff --git a/spec/fixtures/certs/rsa-2048-wrong-public.pem b/spec/fixtures/certs/rsa-2048-wrong-public.pem new file mode 100644 index 00000000..8d496014 --- /dev/null +++ b/spec/fixtures/certs/rsa-2048-wrong-public.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzHAVGaW9j4l3/b4ngcjj +oIoIcnsQEWOMqErb5VhLZMGIq1gEO5qxPDAwooKsNotzcAOB3ZyLn7p5D+dmOrNU +YkYWgYITNGeSifrnVqQugd5Fh1L8K7zOGltUo2UtjbN4uJ56tzxBMZp2wejs2/Qu +0eu0xZK3To+YkDcWOk92rmNgmUSQC/kNyIOj+yBvOo3wTk6HvbhoIarCgJ6Lay1v +/hMLyQLzwRY/Qfty1FTIDyTv2dch47FsfkZ1KAL+MbUnHuCBPzGxRjXa8Iy9Z7YG +xrYasUt1b0um64bscxoIiCu8yLL8jlg01Rwrjr/MTwKRhwXlMp8B7HTonwtaG6ar +JwIDAQAB +-----END PUBLIC KEY----- diff --git a/spec/fixtures/certs/rsa-4096-private.pem b/spec/fixtures/certs/rsa-4096-private.pem new file mode 100644 index 00000000..ddee0731 --- /dev/null +++ b/spec/fixtures/certs/rsa-4096-private.pem @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- diff --git a/spec/fixtures/certs/rsa-4096-public.pem b/spec/fixtures/certs/rsa-4096-public.pem new file mode 100644 index 00000000..01055eb0 --- /dev/null +++ b/spec/fixtures/certs/rsa-4096-public.pem @@ -0,0 +1,14 @@ +-----BEGIN PUBLIC KEY----- +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAqETmgWBi5rCmb7euJplA +/9xs65+bncc9Yvs5zjyycXSW82JfRuyguGm0OvA2wog24dR4N2kT/87DcGtp5JqJ +WADVFNr+2V2r6i57/OMLruRpn3p2r95dmo0COE+BxPFl7XEBT8JbH57ZtpgcB3/x +kS14nLOWFf96hrXPlXJC+VMVKVZmA8k2LRh42vT5wUf4U0Doy/p7yFNSFFa6Q8ww +e4TBy/z/f+rhFD1w8rxlYjallee/ocm7bjZCwbJGMm7orLViqWfsFX3O35PeoJ5h +/7uJ7iRwvTFERkTdwWP/0BeKBeItBR3YFc2mut+V9W+WKRkMSL6Crc+oVSx3p8aB +7j9SZFzQiRtes4BYETpX1xl2mgIq5hvsFbLw7ESrlIodiwUMTrSIid2DQ6q80kv1 +zXPr4+Id6L0sJLxPCaXnTmNtasSwyedJJYxLjwhHJwtzFAeaq18H3O791YKhjAJ6 +YxK3zJ59jTE6Pkvqjq183f2PGHVRvgSN7aCmI6MBUUB5wDP2K8zX2sh40/uPDVSd +6ei1vl3DpPk+h8iExx6AzbohfqZ+5RUUNx127L3MaQvOVC5TxV+R99gwKW++wzcV +uO3m2KqVUj+K1uYBy3KBCUMBbckpEWGbN++jcdV5oJX6fsC66nOmKlntYwCL/pRw +w+oLsbzF8J3dxeDbKNF9JDsCAwEAAQ== +-----END PUBLIC KEY----- diff --git a/spec/jwt_spec.rb b/spec/jwt_spec.rb index bd134383..738c2815 100644 --- a/spec/jwt_spec.rb +++ b/spec/jwt_spec.rb @@ -1,500 +1,412 @@ -# encoding: utf-8 -require 'helper' +require 'spec_helper' +require 'jwt' describe JWT do - before do - @payload = { 'foo' => 'bar', 'exp' => Time.now.to_i + 1, 'nbf' => Time.now.to_i - 1 } + let(:payload) { { 'user_id' => 'some@user.tld' } } + + let :data do + { + :secret => 'My$ecretK3y', + :rsa_private => OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'rsa-2048-private.pem'))), + :rsa_public => OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'rsa-2048-public.pem'))), + :wrong_rsa_private => OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'rsa-2048-wrong-public.pem'))), + :wrong_rsa_public => OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'rsa-2048-wrong-public.pem'))), + 'ES256_private' => OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'ec256-private.pem'))), + 'ES256_public' => OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'ec256-public.pem'))), + 'ES384_private' => OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'ec384-private.pem'))), + 'ES384_public' => OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'ec384-public.pem'))), + 'ES512_private' => OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'ec512-private.pem'))), + 'ES512_public' => OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'ec512-public.pem'))), + 'NONE' => 'eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJ1c2VyX2lkIjoic29tZUB1c2VyLnRsZCJ9.', + 'HS256' => 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic29tZUB1c2VyLnRsZCJ9.tCGvlClld0lbQ3NZaH8y53n5RSBr3zlS4Oy5bXqvzZQ', + 'HS384' => 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzM4NCJ9.eyJ1c2VyX2lkIjoic29tZUB1c2VyLnRsZCJ9.sj1gc01SawlJSrPZgmveifJ8CzZRYAWjejWm4FRaGaAISESJ9Ncf12fCz2vHrITm', + 'HS512' => 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJ1c2VyX2lkIjoic29tZUB1c2VyLnRsZCJ9.isjhsWMZpRQOWw6LKtlY4L6tMDNkLr0qZ3bQe_xRFXWhzVvJlkclTbLVa1J6Dlj2WyZ_I1jEobTaFMDoXPzwWg', + 'RS256' => 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ1c2VyX2lkIjoic29tZUB1c2VyLnRsZCJ9.u82QrhjZTtwve5akvfWS_4LPywbkb1Yp0nUwZJWtTW0ID7dY9rRiQF5KGj2UDLZotqRlUjyNQgE_hB5BBzICDQdCjQHQoYWE5n_D2wV4PMu7Qg3FVKoBFbf8ee6irodu10fgYxpUIZtvbWw52_6k6A9IoSLSzx_lCcxoVGdW90dUuKhBcZkDtY5WNuQg7MiDthupSL1-V4Y1jmT_7o8tLNGFiocyZfGNw4yGpEOGNvD5WePNit0xsnbj6dEquovUvSFKsMaQXp2PVDEkLOiLMcyk0RrHqrHw2eNSCquWTH8PhX5Up-CVmjQM5zF9ibkaiq8NyPtsy-7rgtbyVMqXBQ', + 'RS384' => 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzM4NCJ9.eyJ1c2VyX2lkIjoic29tZUB1c2VyLnRsZCJ9.2_jPwOsUWJ-3r6lXMdJGPdhLNJQSSEmY2mrDXCwNJk-2YhMIqKAzJJCbyso_A1hS7BVkXmHt54RCcNJXroZBOgmGavCcYTPMaT6sCvVVvJJ_wn7jzKHNAJfL5nWeynTQIBWmL-m_v9QpZAgPALdeqjPRv4JHePZm23kvrUgQOxef2ldXv1l6IB3zfF72uEbk9T5pKBvgeeeQ46xm_HtkpXqMdqcTHawUXeXhuiWxuWfy9pAvhm8ivxwJhiQ15-sQNBlS9lG1_gQz1xaZ_Ou_n1nhNfGwpK5HeS0AgmqsqyCOvaGHeAuAOPZ_dSC3cFKu2AP7kc6_AKBgwJzh4agkXg', + 'RS512' => 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzUxMiJ9.eyJ1c2VyX2lkIjoic29tZUB1c2VyLnRsZCJ9.abwof7BqTvuLkN69OhEuFTP7vjGzfvAvooQdwIRne_a88MsjCq31n4UPvyIlY9_8u69rpU79RbMsrq_UZ6L85zP83EcyYI-HOfFZgYDAL3DJ7biBD99JTzyOsH_2i_E6yCkevjEX6uL_Am_C7jpWyePJQkYzTFni6mW4W1T9UobiVGA1tIZ-XOJDPHHxZkGu6W8lKW0UCsr9Ge2SCSlTs_LDSOa34gqMC5GP89unhLqSMqEMJ_Nm6Rj0rnmk87wBZM-b04LLteWuEU59QDNa4nMTjfXW74U4hX9n5EECDPQdQMecgxlUbFunAfZaoNzP4m7H4vux2FzYkjkXhdqnnw', + 'ES256' => '', + 'ES384' => '', + 'ES512' => '' + } + end + + after(:each) do + expect(OpenSSL.errors).to be_empty end - it 'encodes and decodes JWTs' do - secret = 'secret' - jwt = JWT.encode(@payload, secret) - decoded_payload = JWT.decode(jwt, secret) - expect(decoded_payload).to include(@payload) - end + context 'alg: NONE' do + let(:alg) { 'none' } - it 'encodes and decodes JWTs for RSA signatures' do - private_key = OpenSSL::PKey::RSA.generate(512) - jwt = JWT.encode(@payload, private_key, 'RS256') - decoded_payload = JWT.decode(jwt, private_key.public_key) - expect(decoded_payload).to include(@payload) - end + it 'should generate a valid token' do + token = JWT.encode payload, nil, alg - it 'encodes and decodes JWTs for ECDSA P-256 signatures' do - private_key = OpenSSL::PKey::EC.new('prime256v1') - private_key.generate_key - public_key = OpenSSL::PKey::EC.new(private_key) - public_key.private_key = nil - jwt = JWT.encode(@payload, private_key, 'ES256') - decoded_payload = JWT.decode(jwt, public_key) - expect(decoded_payload).to include(@payload) - end + expect(token).to eq data['NONE'] + end - it 'encodes and decodes JWTs for ECDSA P-384 signatures' do - private_key = OpenSSL::PKey::EC.new('secp384r1') - private_key.generate_key - public_key = OpenSSL::PKey::EC.new(private_key) - public_key.private_key = nil - jwt = JWT.encode(@payload, private_key, 'ES384') - decoded_payload = JWT.decode(jwt, public_key) - expect(decoded_payload).to include(@payload) - end + it 'should decode a valid token' do + jwt_payload, header = JWT.decode data['NONE'], nil, false - it 'encodes and decodes JWTs for ECDSA P-521 signatures' do - private_key = OpenSSL::PKey::EC.new('secp521r1') - private_key.generate_key - public_key = OpenSSL::PKey::EC.new(private_key) - public_key.private_key = nil - jwt = JWT.encode(@payload, private_key, 'ES512') - decoded_payload = JWT.decode(jwt, public_key) - expect(decoded_payload).to include(@payload) + expect(header['alg']).to eq alg + expect(jwt_payload).to eq payload + end end - it 'encodes and decodes JWTs with custom header fields' do - private_key = OpenSSL::PKey::RSA.generate(512) - jwt = JWT.encode(@payload, private_key, 'RS256', 'kid' => 'default') - decoded_payload = JWT.decode(jwt) do |header| - expect(header['kid']).to eq('default') - private_key.public_key + %w(HS256 HS384 HS512).each do |alg| + context "alg: #{alg}" do + it 'should generate a valid token' do + token = JWT.encode payload, data[:secret], alg + + expect(token).to eq data[alg] + end + + it 'should decode a valid token' do + jwt_payload, header = JWT.decode data[alg], data[:secret] + + expect(header['alg']).to eq alg + expect(jwt_payload).to eq payload + end + + it 'wrong secret should raise JWT::DecodeError' do + expect do + JWT.decode data[alg], 'wrong_secret' + end.to raise_error JWT::DecodeError + end + + it 'wrong secret and verify = false should not raise JWT::DecodeError' do + expect do + JWT.decode data[alg], 'wrong_secret', false + end.not_to raise_error + end end - expect(decoded_payload).to include(@payload) end - it 'raises encode exception when ECDSA algorithm does not match key' do - private_key = OpenSSL::PKey::EC.new('prime256v1') - private_key.generate_key - expect do - JWT.encode(@payload, private_key, 'ES512') - end.to raise_error(JWT::IncorrectAlgorithm, 'payload algorithm is ES512 but ES256 signing key was provided') - end + %w(RS256 RS384 RS512).each do |alg| + context "alg: #{alg}" do + it 'should generate a valid token' do + token = JWT.encode payload, data[:rsa_private], alg - it 'decodes valid JWTs' do - example_payload = { 'hello' => 'world' } - example_secret = 'secret' - example_jwt = 'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJoZWxsbyI6ICJ3b3JsZCJ9.tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8' - decoded_payload = JWT.decode(example_jwt, example_secret) - expect(decoded_payload).to include(example_payload) - end + expect(token).to eq data[alg] + end - it 'decodes valid ES512 JWTs' do - example_payload = { 'hello' => 'world' } - example_jwt = 'eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJoZWxsbyI6IndvcmxkIn0.AQx1MqdTni6KuzfOoedg2-7NUiwe-b88SWbdmviz40GTwrM0Mybp1i1tVtmTSQ91oEXGXBdtwsN6yalzP9J-sp2YATX_Tv4h-BednbdSvYxZsYnUoZ--ZUdL10t7g8Yt3y9hdY_diOjIptcha6ajX8yzkDGYG42iSe3f5LywSuD6FO5c' - pubkey_pem = "-----BEGIN PUBLIC KEY-----\nMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAcpkss6wI7PPlxj3t7A1RqMH3nvL4\nL5Tzxze/XeeYZnHqxiX+gle70DlGRMqqOq+PJ6RYX7vK0PJFdiAIXlyPQq0B3KaU\ne86IvFeQSFrJdCc0K8NfiH2G1loIk3fiR+YLqlXk6FAeKtpXJKxR1pCQCAM+vBCs\nmZudf1zCUZ8/4eodlHU=\n-----END PUBLIC KEY-----" - pubkey = OpenSSL::PKey::EC.new pubkey_pem - decoded_payload = JWT.decode(example_jwt, pubkey) - expect(decoded_payload).to include(example_payload) - end + it 'should decode a valid token' do + jwt_payload, header = JWT.decode data[alg], data[:rsa_public] - it 'decodes valid JWTs with iss' do - example_payload = { 'hello' => 'world', 'iss' => 'jwtiss' } - example_secret = 'secret' - example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaXNzIjoiand0aXNzIn0.nTZkyYfpGUyKULaj45lXw_1gXXjHvGW4h5V7okHdUqQ' - decoded_payload = JWT.decode(example_jwt, example_secret, true, 'iss' => 'jwtiss') - expect(decoded_payload).to include(example_payload) - end + expect(header['alg']).to eq alg + expect(jwt_payload).to eq payload + end - context 'issuer claim verifications' do - it 'raises invalid issuer when "iss" claim does not match' do - example_secret = 'secret' + it 'wrong key should raise JWT::DecodeError' do + key = OpenSSL::PKey.read File.read(File.join(CERT_PATH, 'rsa-2048-wrong-public.pem')) - example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaXNzIjoiand0aXNzIn0.nTZkyYfpGUyKULaj45lXw_1gXXjHvGW4h5V7okHdUqQ' - expect { JWT.decode(example_jwt, example_secret, true, verify_iss: true, iss: 'jwt_iss') }.to raise_error(JWT::InvalidIssuerError, /Expected jwt_iss, received jwtiss/) - end + expect do + JWT.decode data[alg], key + end.to raise_error JWT::DecodeError + end - it 'raises invalid issuer when "iss" claim is missing in payload' do - example_secret = 'secret' + it 'wrong key and verify = false should not raise JWT::DecodeError' do + key = OpenSSL::PKey.read File.read(File.join(CERT_PATH, 'rsa-2048-wrong-public.pem')) - example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIn0.bqxXg9VwcbXKoiWtp-osd0WKPX307RjcN7EuXbdq-CE' - expect { JWT.decode(example_jwt, example_secret, true, verify_iss: true, iss: 'jwt_iss') }.to raise_error(JWT::InvalidIssuerError, /received /) + expect do + JWT.decode data[alg], key, false + end.not_to raise_error + end end + end - it 'does not raise invalid issuer when verify_iss is set to false (default option)' do - example_secret = 'secret' + %w(ES256 ES384 ES512).each do |alg| + context "alg: #{alg}" do + before(:each) do + data[alg] = JWT.encode payload, data["#{alg}_private"], alg + end - example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaXNzIjoiand0aXNzIn0.nTZkyYfpGUyKULaj45lXw_1gXXjHvGW4h5V7okHdUqQ' - expect { JWT.decode(example_jwt, example_secret, true, 'iss' => 'jwt_iss') }.not_to raise_error - end + let(:wrong_key) { OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'ec256-wrong-public.pem'))) } - it 'does not raise invalid issuer when correct "iss" is in payload' do - example_secret = 'secret' + it 'should generate a valid token' do + jwt_payload, header = JWT.decode data[alg], data["#{alg}_public"] - example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaXNzIjoiand0X2lzcyJ9.mwbyRJJZJR1C5lBt8WOLg0ZMuwP9VGDf5HiQtFhd-eA' - expect { JWT.decode(example_jwt, example_secret, true, :verify_iss => true, 'iss' => 'jwt_iss') }.not_to raise_error + expect(header['alg']).to eq alg + expect(jwt_payload).to eq payload + end + + it 'should decode a valid token' do + jwt_payload, header = JWT.decode data[alg], data["#{alg}_public"] + + expect(header['alg']).to eq alg + expect(jwt_payload).to eq payload + end + + it 'wrong key should raise JWT::DecodeError' do + expect do + JWT.decode data[alg], wrong_key + end.to raise_error JWT::DecodeError + end + + it 'wrong key and verify = false should not raise JWT::DecodeError' do + expect do + JWT.decode data[alg], wrong_key, false + end.not_to raise_error + end end end - it 'decodes valid JWTs with iat' do - example_payload = { 'hello' => 'world', 'iat' => 1_425_917_209 } - example_secret = 'secret' - example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaWF0IjoxNDI1OTE3MjA5fQ.m4F-Ugo7aLnLunBBO3BeDidyWMx8T9eoJz6FW2rgQhU' - decoded_payload = JWT.decode(example_jwt, example_secret, true) - expect(decoded_payload).to include(example_payload) - end + context 'Invalid' do + it 'algorithm should raise NotImplementedError' do + expect do + JWT.encode payload, 'secret', 'HS255' + end.to raise_error NotImplementedError + end - it 'raises decode exception when iat is invalid' do - # example_payload = {'hello' => 'world', 'iat' => '1425917209'} - example_secret = 'secret' - example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaWF0IjoiMTQyNTkxNzIwOSJ9.Mn_vk61xWjIhbXFqAB0nFmNkDiCmfzUgl_LaCKRT6S8' - expect { JWT.decode(example_jwt, example_secret, true, verify_iat: true) }.to raise_error(JWT::InvalidIatError) - end + it 'ECDSA curve_name should raise JWT::IncorrectAlgorithm' do + key = OpenSSL::PKey::EC.new 'secp256k1' + key.generate_key - it 'raises decode exception when iat is in the future' do - invalid_payload = @payload.clone - invalid_payload['iat'] = Time.now.to_i + 3 - secret = 'secret' - jwt = JWT.encode(invalid_payload, secret) - expect { JWT.decode(jwt, secret, true, verify_iat: true) }.to raise_error(JWT::InvalidIatError) - end + expect do + JWT.encode payload, key, 'ES256' + end.to raise_error JWT::IncorrectAlgorithm - it 'performs normal decode if iat is within leeway' do - invalid_payload = @payload.clone - invalid_payload['iat'] = Time.now.to_i + 3 - secret = 'secret' - jwt = JWT.encode(invalid_payload, secret) - expect { JWT.decode(jwt, secret, true, verify_iat: true, leeway: 3) }.to_not raise_error - end + token = JWT.encode payload, data['ES256_private'], 'ES256' + key.private_key = nil - it 'decodes valid JWTs with jti' do - example_payload = { 'hello' => 'world', 'iat' => 1_425_917_209, 'jti' => Digest::MD5.hexdigest('secret:1425917209') } - example_secret = 'secret' - example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaWF0IjoxNDI1OTE3MjA5LCJqdGkiOiI1NWM3NzZlMjFmN2NiZDg3OWMwNmZhYzAxOGRhYzQwMiJ9.ET0hb-VTUOL3M22oG13ofzvGPLMAncbF8rdNDIqo8tg' - decoded_payload = JWT.decode(example_jwt, example_secret, true, 'jti' => Digest::MD5.hexdigest('secret:1425917209')) - expect(decoded_payload).to include(example_payload) + expect do + JWT.decode token, key + end.to raise_error JWT::IncorrectAlgorithm + end end - it 'raises decode exception when jti is invalid' do - # example_payload = {'hello' => 'world', 'iat' => 1425917209, 'jti' => Digest::MD5.hexdigest('secret:1425917209')} - example_secret = 'secret' - example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiaWF0IjoxNDI1OTE3MjA5LCJqdGkiOiI1NWM3NzZlMjFmN2NiZDg3OWMwNmZhYzAxOGRhYzQwMiJ9.ET0hb-VTUOL3M22oG13ofzvGPLMAncbF8rdNDIqo8tg' - expect { JWT.decode(example_jwt, example_secret, true, :verify_jti => true, 'jti' => Digest::MD5.hexdigest('secret:1425922032')) }.to raise_error(JWT::InvalidJtiError) - # expect{ JWT.decode(example_jwt, example_secret) }.to raise_error(JWT::InvalidJtiError) - end + context 'Verify' do + context 'algorithm' do + it 'should raise JWT::IncorrectAlgorithm on missmatch' do + token = JWT.encode payload, data[:secret], 'HS512' - it 'raises decode exception when jti without iat' do - # example_payload = {'hello' => 'world', 'jti' => Digest::MD5.hexdigest('secret:1425917209')} - example_secret = 'secret' - example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwianRpIjoiNTVjNzc2ZTIxZjdjYmQ4NzljMDZmYWMwMThkYWM0MDIifQ.n0foJCnCM_-_xUvG_TOmR9mYpL2y0UqZOD_gv33djeE' - expect { JWT.decode(example_jwt, example_secret, true, :verify_jti => true, 'jti' => Digest::MD5.hexdigest('secret:1425922032')) }.to raise_error(JWT::InvalidJtiError) - end + expect do + JWT.decode token, data[:secret], true, algorithm: 'HS384' + end.to raise_error JWT::IncorrectAlgorithm - context 'aud claim verifications' do - it 'decodes valid JWTs with aud' do - example_payload = { 'hello' => 'world', 'aud' => 'url:pnd' } - example_payload2 = { 'hello' => 'world', 'aud' => ['url:pnd', 'aud:yes'] } - example_secret = 'secret' - example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiYXVkIjoidXJsOnBuZCJ9._gT5veUtNiZD7wLEC6Gd0-nkQV3cl1z8G0zXq8qcd-8' - example_jwt2 = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiYXVkIjpbInVybDpwbmQiLCJhdWQ6eWVzIl19.qNPNcT4X9B5uI91rIwbW2bIPTsp8wbRYW3jkZkrmqbQ' - decoded_payload = JWT.decode(example_jwt, example_secret, true, :verify_aud => true, 'aud' => 'url:pnd') - decoded_payload2 = JWT.decode(example_jwt2, example_secret, true, :verify_aud => true, 'aud' => 'url:pnd') - expect(decoded_payload).to include(example_payload) - expect(decoded_payload2).to include(example_payload2) + expect do + JWT.decode token, data[:secret], true, algorithm: 'HS512' + end.not_to raise_error + end end - it 'raises deode exception when aud is invalid' do - # example_payload = {'hello' => 'world', 'aud' => 'url:pnd'} - example_secret = 'secret' - example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwiYXVkIjoidXJsOnBuZCJ9._gT5veUtNiZD7wLEC6Gd0-nkQV3cl1z8G0zXq8qcd-8' - expect { JWT.decode(example_jwt, example_secret, true, verify_aud: true, aud: 'wrong:aud') }.to raise_error(JWT::InvalidAudError) - end + context 'expiration claim' do + let(:exp) { Time.now.to_i - 5 } + let(:leeway) { 10 } - it 'raises deode exception when aud is missing' do - # JWT.encode('hello' => 'world', 'secret') - example_secret = 'secret' - example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIn0.bqxXg9VwcbXKoiWtp-osd0WKPX307RjcN7EuXbdq-CE' - expect { JWT.decode(example_jwt, example_secret, true, verify_aud: true, aud: 'url:pnd') }.to raise_error(JWT::InvalidAudError) + let :token do + payload.merge!(exp: exp) + + JWT.encode payload, data[:secret] + end + + it 'old token should raise JWT::ExpiredSignature' do + expect do + JWT.decode token, data[:secret] + end.to raise_error JWT::ExpiredSignature + end + + it 'should handle leeway' do + expect do + JWT.decode token, data[:secret], true, leeway: leeway + end.not_to raise_error + end end - end - it 'decodes valid JWTs with sub' do - example_payload = { 'hello' => 'world', 'sub' => 'subject' } - example_secret = 'secret' - example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIiwic3ViIjoic3ViamVjdCJ9.QUnNVZm4SPB4vP2zY9m1LoUSOx-5oGXBhj7R89D_UtA' - decoded_payload = JWT.decode(example_jwt, example_secret, true, 'sub' => 'subject') - expect(decoded_payload).to include(example_payload) - end + context 'not before claim' do + let(:nbf) { Time.now.to_i + 5 } + let(:leeway) { 10 } - it 'raises decode exception when verify_sub is set but the JWT does not contain a sub' do - example_payload = {'foo' => 'bar'} - example_secret = 'secret' - example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmb28iOiJiYXIifQ.bVhBeMrW5g33Vi4FLSLn7aqcmAiupmmw-AY17YxCYLI' - expect { JWT.decode(example_jwt, example_secret, true, verify_sub: true, sub: 'subject') }.to raise_error(JWT::InvalidSubError) - end + let :token do + payload.merge!(nbf: nbf) - it 'raise decode exception when the sub does not match' do - example_payload = {'sub' => 'subject', 'foo' => 'bar'} - example_secret = 'secret' - example_jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzdWJqZWN0IiwiZm9vIjoiYmFyIn0.RUkJZgPcK3eOU3iR301nsZa3MSA936fgu03_UoamVuo' - expect { JWT.decode(example_jwt, example_secret, true, verify_sub: true, sub: 'another_subject') }.to raise_error(JWT::InvalidSubError) - end + JWT.encode payload, data[:secret] + end - it 'raises decode exception when the token is invalid' do - example_secret = 'secret' - # Same as above exmaple with some random bytes replaced - example_jwt = 'eyJhbGciOiAiSFMyNTYiLCAidHiMomlwIjogIkJ9.eyJoZWxsbyI6ICJ3b3JsZCJ9.tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8' - expect { JWT.decode(example_jwt, example_secret) }.to raise_error(JWT::DecodeError) - end + it 'future token should raise JWT::ImmatureSignature' do + expect do + JWT.decode token, data[:secret] + end.to raise_error JWT::ImmatureSignature + end - it 'raises verification exception with wrong hmac key' do - right_secret = 'foo' - bad_secret = 'bar' - jwt_message = JWT.encode(@payload, right_secret, 'HS256') - expect { JWT.decode(jwt_message, bad_secret) }.to raise_error(JWT::VerificationError) - end + it 'should handle leeway' do + expect do + JWT.decode token, data[:secret], true, leeway: leeway + end.not_to raise_error + end + end - it 'raises decode exception when ECDSA algorithm does not match key' do - right_private_key = OpenSSL::PKey::EC.new('prime256v1') - right_private_key.generate_key - right_public_key = OpenSSL::PKey::EC.new(right_private_key) - right_public_key.private_key = nil - bad_private_key = OpenSSL::PKey::EC.new('secp384r1') - bad_private_key.generate_key - bad_public_key = OpenSSL::PKey::EC.new(bad_private_key) - bad_public_key.private_key = nil - jwt = JWT.encode(@payload, right_private_key, 'ES256') - expect do - JWT.decode(jwt, bad_public_key) - end.to raise_error(JWT::IncorrectAlgorithm, 'payload algorithm is ES256 but ES384 verification key was provided') - end + context 'issuer claim' do + let(:iss) { 'ruby-jwt-gem' } + let(:invalid_token) { JWT.encode payload, data[:secret] } - it 'raises verification exception with wrong rsa key' do - right_private_key = OpenSSL::PKey::RSA.generate(512) - bad_private_key = OpenSSL::PKey::RSA.generate(512) - jwt = JWT.encode(@payload, right_private_key, 'RS256') - expect { JWT.decode(jwt, bad_private_key.public_key) }.to raise_error(JWT::VerificationError) - end + let :token do + iss_payload = payload.merge(iss: iss) + JWT.encode iss_payload, data[:secret] + end - it 'raises verification exception with wrong ECDSA key' do - right_private_key = OpenSSL::PKey::EC.new('prime256v1') - right_private_key.generate_key - bad_private_key = OpenSSL::PKey::EC.new('prime256v1') - bad_private_key.generate_key - bad_public_key = OpenSSL::PKey::EC.new(bad_private_key) - bad_public_key.private_key = nil - jwt = JWT.encode(@payload, right_private_key, 'ES256') - expect { JWT.decode(jwt, bad_public_key) }.to raise_error(JWT::VerificationError) - end + it 'if verify_iss is set to false (default option) should not raise JWT::InvalidIssuerError' do + expect do + JWT.decode token, data[:secret], true, iss: iss + end.not_to raise_error + end - it 'raises decode exception with invalid signature' do - example_secret = 'secret' - example_jwt = 'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJoZWxsbyI6ICJ3b3JsZCJ9.' - expect { JWT.decode(example_jwt, example_secret) }.to raise_error(JWT::DecodeError) - end + it 'invalid iss should raise JWT::InvalidIssuerError' do + expect do + JWT.decode token, data[:secret], true, iss: 'wrong-issuer', verify_iss: true + end.to raise_error JWT::InvalidIssuerError + end - it 'raises decode exception with nonexistent header' do - expect { JWT.decode('..stuff') }.to raise_error(JWT::DecodeError) - end + it 'with missing iss claim should raise JWT::InvalidIssuerError' do + missing_iss_claim_token = JWT.encode payload, data[:secret] - it 'raises decode exception with nonexistent payload' do - expect { JWT.decode('eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9..stuff') }.to raise_error(JWT::DecodeError) - end + expect do + JWT.decode missing_iss_claim_token, data[:secret], true, verify_iss: true, iss: iss + end.to raise_error(JWT::InvalidIssuerError, /received /) + end - it 'raises decode exception with nil jwt' do - expect { JWT.decode(nil) }.to raise_error(JWT::DecodeError) - end + it 'valid iss should not raise JWT::InvalidIssuerError' do + expect do + JWT.decode token, data[:secret], true, iss: iss, verify_iss: true + end.not_to raise_error + end + end - it 'allows decoding without key' do - right_secret = 'foo' - bad_secret = 'bar' - jwt = JWT.encode(@payload, right_secret) - decoded_payload = JWT.decode(jwt, bad_secret, false) - expect(decoded_payload).to include(@payload) - end + context 'issued at claim' do + let(:iat) { Time.now.to_i } + let(:new_payload) { payload.merge(iat: iat) } + let(:token) { JWT.encode new_payload, data[:secret] } + let(:invalid_token) { JWT.encode new_payload.merge('iat' => iat + 60), data[:secret] } + let(:leeway) { 30 } + + it 'invalid iat should raise JWT::InvalidIatError' do + expect do + JWT.decode invalid_token, data[:secret], true, verify_iat: true + end.to raise_error JWT::InvalidIatError + end - it 'checks the key when verify is truthy' do - right_secret = 'foo' - bad_secret = 'bar' - jwt = JWT.encode(@payload, right_secret) - verify = 'yes' =~ /^y/i - expect { JWT.decode(jwt, bad_secret, verify) }.to raise_error(JWT::DecodeError) - end + it 'should accept leeway' do + expect do + JWT.decode invalid_token, data[:secret], true, verify_iat: true, leeway: 70 + end.to_not raise_error + end - it 'raises exception on unsupported crypto algorithm' do - expect { JWT.encode(@payload, 'secret', 'HS1024') }.to raise_error(NotImplementedError) - end + it 'valid iat should not raise JWT::InvalidIatError' do + expect do + JWT.decode token, data[:secret], true, verify_iat: true + end.to_not raise_error + end + end - it 'raises exception when decoded with a different algorithm than it was encoded with' do - jwt = JWT.encode(@payload, 'foo', 'HS384') - expect { JWT.decode(jwt, 'foo', true, algorithm: 'HS512') }.to raise_error(JWT::IncorrectAlgorithm) - end + context 'audience claim' do + let(:simple_aud) { 'ruby-jwt-audience' } + let(:array_aud) { %w(ruby-jwt-aud test-aud ruby-ruby-ruby) } - it 'does not raise exception when encoded with the expected algorithm' do - jwt = JWT.encode(@payload, 'foo', 'HS512') - JWT.decode(jwt, 'foo', true, algorithm: 'HS512') - end + let :simple_token do + new_payload = payload.merge('aud' => simple_aud) + JWT.encode new_payload, data[:secret] + end - it 'encodes and decodes plaintext JWTs' do - jwt = JWT.encode(@payload, nil, nil) - expect(jwt.split('.').length).to eq(2) - decoded_payload = JWT.decode(jwt, nil, nil) - expect(decoded_payload).to include(@payload) - end + let :array_token do + new_payload = payload.merge('aud' => array_aud) + JWT.encode new_payload, data[:secret] + end - it 'requires a signature segment when verify is truthy' do - jwt = JWT.encode(@payload, nil, nil) - expect(jwt.split('.').length).to eq(2) - expect { JWT.decode(jwt, nil, true) }.to raise_error(JWT::DecodeError) - end + it 'invalid aud should raise JWT::InvalidAudError' do + expect do + JWT.decode simple_token, data[:secret], true, aud: 'wrong audience', verify_aud: true + end.to raise_error JWT::InvalidAudError - it 'does not use == to compare digests' do - secret = 'secret' - jwt = JWT.encode(@payload, secret) - crypto_segment = jwt.split('.').last + expect do + JWT.decode array_token, data[:secret], true, aud: %w(wrong audience), verify_aud: true + end.to raise_error JWT::InvalidAudError + end - signature = JWT.base64url_decode(crypto_segment) - expect(signature).not_to receive('==') - expect(JWT).to receive(:base64url_decode).with(crypto_segment).once.and_return(signature) - expect(JWT).to receive(:base64url_decode).at_least(:once).and_call_original + it 'valid aud should not raise JWT::InvalidAudError' do + expect do + JWT.decode simple_token, data[:secret], true, 'aud' => simple_aud, :verify_aud => true + end.to_not raise_error - JWT.decode(jwt, secret) - end + expect do + JWT.decode array_token, data[:secret], true, 'aud' => array_aud.first, :verify_aud => true + end.to_not raise_error + end + end - it 'raises error when expired' do - expired_payload = @payload.clone - expired_payload['exp'] = Time.now.to_i - 1 - secret = 'secret' - jwt = JWT.encode(expired_payload, secret) - expect { JWT.decode(jwt, secret) }.to raise_error(JWT::ExpiredSignature) - end + context 'subject claim' do + let(:sub) { 'ruby jwt subject' } - it 'raise ExpiredSignature even when exp claims is a string' do - expired_payload = @payload.clone - expired_payload['exp'] = (Time.now.to_i).to_s - secret = 'secret' - jwt = JWT.encode(expired_payload, secret) - expect { JWT.decode(jwt, secret) }.to raise_error(JWT::ExpiredSignature) - end + let :token do + new_payload = payload.merge('sub' => sub) + JWT.encode new_payload, data[:secret] + end - it 'performs normal decode with skipped expiration check' do - expired_payload = @payload.clone - expired_payload['exp'] = Time.now.to_i - 1 - secret = 'secret' - jwt = JWT.encode(expired_payload, secret) - decoded_payload = JWT.decode(jwt, secret, true, verify_expiration: false) - expect(decoded_payload).to include(expired_payload) - end + let :invalid_token do + new_payload = payload.merge('sub' => 'we are not the druids you are looking for') + JWT.encode new_payload, data[:secret] + end - it 'performs normal decode using leeway' do - expired_payload = @payload.clone - expired_payload['exp'] = Time.now.to_i - 2 - secret = 'secret' - jwt = JWT.encode(expired_payload, secret) - decoded_payload = JWT.decode(jwt, secret, true, leeway: 3) - expect(decoded_payload).to include(expired_payload) - end + it 'invalid sub should raise JWT::InvalidSubError' do + expect do + JWT.decode invalid_token, data[:secret], true, sub: sub, verify_sub: true + end.to raise_error JWT::InvalidSubError + end - it 'raises error when before nbf' do - immature_payload = @payload.clone - immature_payload['nbf'] = Time.now.to_i + 1 - secret = 'secret' - jwt = JWT.encode(immature_payload, secret) - expect { JWT.decode(jwt, secret) }.to raise_error(JWT::ImmatureSignature) - end + it 'valid sub should not raise JWT::InvalidSubError' do + expect do + JWT.decode token, data[:secret], true, 'sub' => sub, :verify_sub => true + end.to_not raise_error + end + end - it 'doesnt raise error when equal to nbf' do - mature_payload = @payload.clone - mature_payload['nbf'] = Time.now.to_i - secret = 'secret' - jwt = JWT.encode(mature_payload, secret) - decoded_payload = JWT.decode(jwt, secret, true, verify_expiration: false) - expect(decoded_payload).to include(mature_payload) - end + context 'jwt id claim' do + let :jti do + new_payload = payload.merge('iat' => Time.now.to_i) + key = data[:secret] + new_payload.merge('jti' => Digest::MD5.hexdigest("#{key}:#{new_payload['iat']}")) + end - it 'doesnt raise error when after nbf' do - mature_payload = @payload.clone - secret = 'secret' - jwt = JWT.encode(mature_payload, secret) - decoded_payload = JWT.decode(jwt, secret, true, verify_expiration: false) - expect(decoded_payload).to include(mature_payload) - end + let(:token) { JWT.encode jti, data[:secret] } - it 'raise ImmatureSignature even when nbf claim is a string' do - immature_payload = @payload.clone - immature_payload['nbf'] = (Time.now.to_i + 1).to_s - secret = 'secret' - jwt = JWT.encode(immature_payload, secret) - expect { JWT.decode(jwt, secret) }.to raise_error(JWT::ImmatureSignature) - end + let :invalid_token do + jti.delete('iat') + JWT.encode jti, data[:secret] + end - it 'performs normal decode with skipped not before check' do - immature_payload = @payload.clone - immature_payload['nbf'] = Time.now.to_i + 2 - secret = 'secret' - jwt = JWT.encode(immature_payload, secret) - decoded_payload = JWT.decode(jwt, secret, true, verify_not_before: false) - expect(decoded_payload).to include(immature_payload) + it 'invalid jti should raise JWT::InvalidJtiError' do + expect do + JWT.decode invalid_token, data[:secret], true, :verify_jti => true, 'jti' => jti['jti'] + end.to raise_error JWT::InvalidJtiError + end + + it 'valid jti should not raise JWT::InvalidJtiError' do + expect do + JWT.decode token, data[:secret], true, verify_jti: true, jti: jti['jti'] + end.to_not raise_error + end + end end - it 'performs normal decode using leeway' do - immature_payload = @payload.clone - immature_payload['nbf'] = Time.now.to_i - 2 - secret = 'secret' - jwt = JWT.encode(immature_payload, secret) - decoded_payload = JWT.decode(jwt, secret, true, leeway: 3) - expect(decoded_payload).to include(immature_payload) + context 'Base64' do + it 'urlsafe replace + / with - _' do + allow(Base64).to receive(:encode64) { 'string+with/non+url-safe/characters_' } + expect(JWT.base64url_encode('foo')).to eq('string-with_non-url-safe_characters_') + end end describe 'secure comparison' do it 'returns true if strings are equal' do - expect(JWT.secure_compare('Foo', 'Foo')).to be true + expect(JWT.secure_compare('Foo', 'Foo')).to eq true end it 'returns false if either input is nil or empty' do [nil, ''].each do |bad| - expect(JWT.secure_compare(bad, 'Foo')).to be false - expect(JWT.secure_compare('Foo', bad)).to be false + expect(JWT.secure_compare(bad, 'Foo')).to eq false + expect(JWT.secure_compare('Foo', bad)).to eq false end end it 'retuns false if the strings are different' do - expect(JWT.secure_compare('Foo', 'Bar')).to be false + expect(JWT.secure_compare('Foo', 'Bar')).to eq false end end - # no method should leave OpenSSL.errors populated - after do - expect(OpenSSL.errors).to be_empty - end - - it 'raise exception on invalid signature' do - pubkey = OpenSSL::PKey::RSA.new(<<-PUBKEY) ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxCaY7425h964bjaoLeUm -SlZ8sK7VtVk9zHbGmZh2ygGYwfuUf2bmMye2Ofv99yDE/rd4loVIAcu7RVvDRgHq -3/CZTnIrSvHsiJQsHBNa3d+F1ihPfzURzf1M5k7CFReBj2SBXhDXd57oRfBQj12w -CVhhwP6kGTAWuoppbIIIBfNF2lE/Nvm7lVVYQqL9xOrP/AQ4xRbpQlB8Ll9sO9Or -SvbWhCDa/LMOWxHdmrcJi6XoSg1vnOyCoKbyAoauTt/XqdkHbkDdQ6HFbJieu9il -LDZZNliPhfENuKeC2MCGVXTEu8Cqhy1w6e4axavLlXoYf4laJIZ/e7au8SqDbY0B -xwIDAQAB ------END PUBLIC KEY----- -PUBKEY - jwt = ( - 'eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJhY2NvdW50cy5nb29nbGUuY29tIiwiY' \ - 'XVkIjoiMTA2MDM1Nzg5MTY4OC5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSI' \ - 'sImNpZCI6IjEwNjAzNTc4OTE2ODguYXBwcy5nb29nbGV1c2VyY29udGVudC5jb' \ - '20iLCJpZCI6IjExNjQ1MjgyNDMwOTg1Njc4MjE2MyIsInRva2VuX2hhc2giOiJ' \ - '0Z2hEOUo4bjhWME4ydmN3NmVNaWpnIiwiaWF0IjoxMzIwNjcwOTc4LCJleHAiO' \ - 'jEzMjA2NzQ4Nzh9.D8x_wirkxDElqKdJBcsIws3Ogesk38okz6MN7zqC7nEAA7' \ - 'wcy1PxsROY1fmBvXSer0IQesAqOW-rPOCNReSn-eY8d53ph1x2HAF-AzEi3GOl' \ - '6hFycH8wj7Su6JqqyEbIVLxE7q7DkAZGaMPkxbTHs1EhSd5_oaKQ6O4xO3ZnnT4' - ) - expect { JWT.decode(jwt, pubkey, true) }.to raise_error(JWT::DecodeError) - end - - describe 'urlsafe base64 encoding' do - it 'replaces + and / with - and _' do - allow(Base64).to receive(:encode64) { 'string+with/non+url-safe/characters_' } - expect(JWT.base64url_encode('foo')).to eq('string-with_non-url-safe_characters_') - end - end - - describe 'decoded_segments' do - it 'allows access to the decoded header and payload' do - secret = 'secret' - jwt = JWT.encode(@payload, secret) - decoded_segments = JWT.decoded_segments(jwt) - expect(decoded_segments.size).to eq(4) - expect(decoded_segments[0]).to eq('typ' => 'JWT', 'alg' => 'HS256') - expect(decoded_segments[1]).to eq(@payload) - end - end end diff --git a/spec/helper.rb b/spec/spec_helper.rb similarity index 65% rename from spec/helper.rb rename to spec/spec_helper.rb index 747cfae2..4deef7d9 100644 --- a/spec/helper.rb +++ b/spec/spec_helper.rb @@ -18,4 +18,15 @@ SimpleCov.start if ENV['COVERAGE'] CodeClimate::TestReporter.start if ENV['CODECLIMATE_REPO_TOKEN'] -require "#{File.dirname(__FILE__)}/../lib/jwt.rb" +CERT_PATH = File.join(File.dirname(__FILE__), 'fixtures', 'certs') + +RSpec.configure do |config| + config.expect_with :rspec do |c| + c.syntax = [:should, :expect] + end + + config.run_all_when_everything_filtered = true + config.filter_run :focus + + config.order = 'random' +end