1defmodule JwtAuthTest do 2 use CouchTestCase 3 4 @moduletag :authentication 5 6 test "jwt auth with HMAC secret", _context do 7 8 secret = "zxczxc12zxczxc12" 9 10 server_config = [ 11 %{ 12 :section => "jwt_keys", 13 :key => "hmac:_default", 14 :value => :base64.encode(secret) 15 }, 16 %{ 17 :section => "jwt_auth", 18 :key => "allowed_algorithms", 19 :value => "HS256, HS384, HS512" 20 } 21 ] 22 23 run_on_modified_server(server_config, fn -> test_fun("HS256", secret) end) 24 run_on_modified_server(server_config, fn -> test_fun("HS384", secret) end) 25 run_on_modified_server(server_config, fn -> test_fun("HS512", secret) end) 26 end 27 28 defmodule RSA do 29 require Record 30 Record.defrecord :public, :RSAPublicKey, 31 Record.extract(:RSAPublicKey, from_lib: "public_key/include/public_key.hrl") 32 Record.defrecord :private, :RSAPrivateKey, 33 Record.extract(:RSAPrivateKey, from_lib: "public_key/include/public_key.hrl") 34 end 35 36 test "jwt auth with RSA secret", _context do 37 require JwtAuthTest.RSA 38 39 private_key = :public_key.generate_key({:rsa, 2048, 17}) 40 public_key = RSA.public( 41 modulus: RSA.private(private_key, :modulus), 42 publicExponent: RSA.private(private_key, :publicExponent)) 43 44 public_pem = :public_key.pem_encode( 45 [:public_key.pem_entry_encode( 46 :SubjectPublicKeyInfo, public_key)]) 47 public_pem = String.replace(public_pem, "\n", "\\n") 48 49 server_config = [ 50 %{ 51 :section => "jwt_keys", 52 :key => "rsa:_default", 53 :value => public_pem 54 }, 55 %{ 56 :section => "jwt_auth", 57 :key => "allowed_algorithms", 58 :value => "RS256, RS384, RS512" 59 } 60 ] 61 62 run_on_modified_server(server_config, fn -> test_fun("RS256", private_key) end) 63 run_on_modified_server(server_config, fn -> test_fun("RS384", private_key) end) 64 run_on_modified_server(server_config, fn -> test_fun("RS512", private_key) end) 65 end 66 67 defmodule EC do 68 require Record 69 Record.defrecord :point, :ECPoint, 70 Record.extract(:ECPoint, from_lib: "public_key/include/public_key.hrl") 71 Record.defrecord :private, :ECPrivateKey, 72 Record.extract(:ECPrivateKey, from_lib: "public_key/include/public_key.hrl") 73 end 74 75 test "jwt auth with EC secret", _context do 76 require JwtAuthTest.EC 77 78 private_key = :public_key.generate_key({:namedCurve, :secp384r1}) 79 point = EC.point(point: EC.private(private_key, :publicKey)) 80 public_key = {point, EC.private(private_key, :parameters)} 81 82 public_pem = :public_key.pem_encode( 83 [:public_key.pem_entry_encode( 84 :SubjectPublicKeyInfo, public_key)]) 85 public_pem = String.replace(public_pem, "\n", "\\n") 86 87 server_config = [ 88 %{ 89 :section => "jwt_keys", 90 :key => "ec:_default", 91 :value => public_pem 92 }, 93 %{ 94 :section => "jwt_auth", 95 :key => "allowed_algorithms", 96 :value => "ES256, ES384, ES512" 97 } 98 ] 99 100 run_on_modified_server(server_config, fn -> test_fun("ES256", private_key) end) 101 run_on_modified_server(server_config, fn -> test_fun("ES384", private_key) end) 102 run_on_modified_server(server_config, fn -> test_fun("ES512", private_key) end) 103 end 104 105 def test_fun(alg, key) do 106 now = DateTime.to_unix(DateTime.utc_now()) 107 {:ok, token} = :jwtf.encode( 108 { 109 [ 110 {"alg", alg}, 111 {"typ", "JWT"} 112 ] 113 }, 114 { 115 [ 116 {"nbf", now - 60}, 117 {"exp", now + 60}, 118 {"sub", "couch@apache.org"}, 119 {"_couchdb.roles", ["testing"] 120 } 121 ] 122 }, key) 123 124 resp = Couch.get("/_session", 125 headers: [authorization: "Bearer #{token}"] 126 ) 127 128 assert resp.body["userCtx"]["name"] == "couch@apache.org" 129 assert resp.body["info"]["authenticated"] == "jwt" 130 end 131 132 test "jwt auth without secret", _context do 133 134 resp = Couch.get("/_session") 135 136 assert resp.body["userCtx"]["name"] == "adm" 137 assert resp.body["info"]["authenticated"] == "default" 138 end 139 140 test "jwt auth with required iss claim", _context do 141 142 secret = "zxczxc12zxczxc12" 143 144 server_config = [ 145 %{ 146 :section => "jwt_auth", 147 :key => "required_claims", 148 :value => "{iss, \"hello\"}" 149 }, 150 %{ 151 :section => "jwt_keys", 152 :key => "hmac:_default", 153 :value => :base64.encode(secret) 154 }, 155 %{ 156 :section => "jwt_auth", 157 :key => "allowed_algorithms", 158 :value => "HS256, HS384, HS512" 159 } 160 ] 161 162 run_on_modified_server(server_config, fn -> good_iss("HS256", secret) end) 163 run_on_modified_server(server_config, fn -> bad_iss("HS256", secret) end) 164 end 165 166 def good_iss(alg, key) do 167 {:ok, token} = :jwtf.encode( 168 { 169 [ 170 {"alg", alg}, 171 {"typ", "JWT"} 172 ] 173 }, 174 { 175 [ 176 {"iss", "hello"}, 177 {"sub", "couch@apache.org"}, 178 {"_couchdb.roles", ["testing"] 179 } 180 ] 181 }, key) 182 183 resp = Couch.get("/_session", 184 headers: [authorization: "Bearer #{token}"] 185 ) 186 187 assert resp.body["userCtx"]["name"] == "couch@apache.org" 188 assert resp.body["userCtx"]["roles"] == ["testing"] 189 assert resp.body["info"]["authenticated"] == "jwt" 190 end 191 192 def bad_iss(alg, key) do 193 {:ok, token} = :jwtf.encode( 194 { 195 [ 196 {"alg", alg}, 197 {"typ", "JWT"} 198 ] 199 }, 200 { 201 [ 202 {"iss", "goodbye"}, 203 {"sub", "couch@apache.org"}, 204 {"_couchdb.roles", ["testing"] 205 } 206 ] 207 }, key) 208 209 resp = Couch.get("/_session", 210 headers: [authorization: "Bearer #{token}"] 211 ) 212 213 assert resp.status_code == 400 214 end 215 216end 217