From caa5acfa7235fa47a9a9659e163aac3487b51682 Mon Sep 17 00:00:00 2001 From: Chris Mckay Date: Mon, 11 Feb 2019 11:07:10 +1000 Subject: [PATCH] password in jwt payload (#823) * make sure we don't put the password in the jwt payload * Made sure password doesn't appear in jwt payload Signed-off-by: Adrian Sutton --- .../ethereum/jsonrpc/JsonRpcHttpService.java | 8 +- .../jsonrpc/JsonRpcHttpServiceLoginTest.java | 100 ++++++++++++++++++ 2 files changed, 107 insertions(+), 1 deletion(-) diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java index 255961db0e..fe5335e720 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpService.java @@ -92,6 +92,7 @@ public class JsonRpcHttpService { private final LabelledMetric requestTimer; @VisibleForTesting public final Optional jwtAuthProvider; + @VisibleForTesting public final Optional jwtAuthOptions; private final Optional credentialAuthProvider; private HttpServer httpServer; @@ -212,6 +213,7 @@ public class JsonRpcHttpService { this.vertx = vertx; this.jsonRpcMethods = methods; this.credentialAuthProvider = credentialAuthProvider; + this.jwtAuthOptions = jwtOptions; jwtAuthProvider = jwtOptions.map(options -> JWTAuth.create(vertx, options)); } @@ -441,7 +443,11 @@ public class JsonRpcHttpService { final JWTOptions options = new JWTOptions().setExpiresInMinutes(5).setAlgorithm("RS256"); - final String token = jwtAuthProvider.get().generateToken(user.principal(), options); + final JsonObject jwtContents = + new JsonObject() + .put("permissions", user.principal().getValue("permissions")) + .put("username", user.principal().getValue("username")); + final String token = jwtAuthProvider.get().generateToken(jwtContents, options); final JsonObject responseBody = new JsonObject().put("token", token); final HttpServerResponse response = routingContext.response(); diff --git a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceLoginTest.java b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceLoginTest.java index 89986f028c..47b872d672 100644 --- a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceLoginTest.java +++ b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/JsonRpcHttpServiceLoginTest.java @@ -31,20 +31,35 @@ import tech.pegasys.pantheon.ethereum.p2p.wire.Capability; import tech.pegasys.pantheon.ethereum.privacy.PrivateTransactionHandler; import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem; +import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; import java.nio.file.Paths; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import io.vertx.core.Vertx; +import io.vertx.core.buffer.Buffer; import io.vertx.core.json.JsonObject; +import io.vertx.ext.auth.KeyStoreOptions; +import io.vertx.ext.auth.PubSecKeyOptions; +import io.vertx.ext.auth.SecretOptions; import io.vertx.ext.auth.User; import io.vertx.ext.auth.jwt.JWTAuth; +import io.vertx.ext.auth.jwt.JWTAuthOptions; +import io.vertx.ext.auth.jwt.impl.JWTAuthProviderImpl; +import io.vertx.ext.jwt.JWK; +import io.vertx.ext.jwt.JWT; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; @@ -227,4 +242,89 @@ public class JsonRpcHttpServiceLoginTest { }); } } + + private JWT makeJwt(final JWTAuthOptions config) + throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException { + final KeyStoreOptions keyStoreOptions = config.getKeyStore(); + if (keyStoreOptions != null) { + final KeyStore ks = KeyStore.getInstance(keyStoreOptions.getType()); + + // synchronize on the class to avoid the case where multiple file accesses will overlap + synchronized (JWTAuthProviderImpl.class) { + final Buffer keystore = vertx.fileSystem().readFileBlocking(keyStoreOptions.getPath()); + + try (InputStream in = new ByteArrayInputStream(keystore.getBytes())) { + ks.load(in, keyStoreOptions.getPassword().toCharArray()); + } + } + + return new JWT(ks, keyStoreOptions.getPassword().toCharArray()); + } else { + // no key file attempt to load pem keys + final JWT jwt = new JWT(); + + final List keys = config.getPubSecKeys(); + + if (keys != null) { + for (final PubSecKeyOptions pubSecKey : config.getPubSecKeys()) { + if (pubSecKey.isSymmetric()) { + jwt.addJWK(new JWK(pubSecKey.getAlgorithm(), pubSecKey.getPublicKey())); + } else { + jwt.addJWK( + new JWK( + pubSecKey.getAlgorithm(), + pubSecKey.isCertificate(), + pubSecKey.getPublicKey(), + pubSecKey.getSecretKey())); + } + } + } + + // TODO: remove once the deprecation ends! + final List secrets = config.getSecrets(); + + if (secrets != null) { + for (final SecretOptions secret : secrets) { + jwt.addSecret(secret.getType(), secret.getSecret()); + } + } + + final List jwks = config.getJwks(); + + if (jwks != null) { + for (final JsonObject jwk : jwks) { + jwt.addJWK(new JWK(jwk)); + } + } + return jwt; + } + } + + @Test + public void loginDoesntPopulateJWTPayloadWithPassword() + throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException { + final RequestBody body = + RequestBody.create(JSON, "{\"username\":\"user\",\"password\":\"pegasys\"}"); + final Request request = new Request.Builder().post(body).url(baseUrl + "/login").build(); + try (final Response resp = client.newCall(request).execute()) { + assertThat(resp.code()).isEqualTo(200); + assertThat(resp.message()).isEqualTo("OK"); + assertThat(resp.body().contentType()).isNotNull(); + assertThat(resp.body().contentType().type()).isEqualTo("application"); + assertThat(resp.body().contentType().subtype()).isEqualTo("json"); + final String bodyString = resp.body().string(); + assertThat(bodyString).isNotNull(); + assertThat(bodyString).isNotBlank(); + + final JsonObject respBody = new JsonObject(bodyString); + final String token = respBody.getString("token"); + assertThat(token).isNotNull(); + final JWT jwt = makeJwt(service.jwtAuthOptions.get()); + + final JsonObject jwtPayload = jwt.decode(token); + final String jwtPayloadString = jwtPayload.encode(); + assertThat(jwtPayloadString.contains("password")).isFalse(); + assertThat(jwtPayloadString.contains("pegasys")).isFalse(); + } + } }