JAMES-1959 Extract JWT to an other maven project
Project: http://git-wip-us.apache.org/repos/asf/james-project/repo Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/f1a087fa Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/f1a087fa Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/f1a087fa Branch: refs/heads/master Commit: f1a087fa60217383969f2a281703f8cf0e0d8efb Parents: 3ac2368 Author: benwa <btell...@linagora.com> Authored: Wed Mar 8 17:25:54 2017 +0700 Committer: benwa <btell...@linagora.com> Committed: Wed Mar 15 09:01:53 2017 +0700 ---------------------------------------------------------------------- .../java/org/apache/james/jmap/JMAPModule.java | 7 + server/pom.xml | 6 + server/protocols/jmap/pom.xml | 4 + .../james/jmap/JWTAuthenticationStrategy.java | 2 +- .../james/jmap/crypto/JwtTokenVerifier.java | 63 ----- .../crypto/MissingOrInvalidKeyException.java | 22 -- .../james/jmap/crypto/PublicKeyProvider.java | 44 ---- .../james/jmap/crypto/PublicKeyReader.java | 56 ----- .../jmap/JWTAuthenticationStrategyTest.java | 2 +- .../james/jmap/crypto/JwtTokenVerifierTest.java | 124 ---------- .../jmap/crypto/PublicKeyProviderTest.java | 74 ------ .../james/jmap/crypto/PublicKeyReaderTest.java | 62 ----- server/protocols/jwt/pom.xml | 245 +++++++++++++++++++ .../org/apache/james/jwt/JwtConfiguration.java | 34 +++ .../org/apache/james/jwt/JwtTokenVerifier.java | 63 +++++ .../james/jwt/MissingOrInvalidKeyException.java | 22 ++ .../org/apache/james/jwt/PublicKeyProvider.java | 44 ++++ .../org/apache/james/jwt/PublicKeyReader.java | 56 +++++ .../apache/james/jwt/JwtTokenVerifierTest.java | 119 +++++++++ .../apache/james/jwt/PublicKeyProviderTest.java | 67 +++++ .../apache/james/jwt/PublicKeyReaderTest.java | 62 +++++ 21 files changed, 731 insertions(+), 447 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/james-project/blob/f1a087fa/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPModule.java ---------------------------------------------------------------------- diff --git a/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPModule.java b/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPModule.java index 7269cb1..eca11aa 100644 --- a/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPModule.java +++ b/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPModule.java @@ -34,6 +34,7 @@ import org.apache.james.jmap.utils.HtmlTextExtractor; import org.apache.james.jmap.utils.MailboxBasedHtmlTextExtractor; import org.apache.james.jmap.utils.SystemMailboxesProvider; import org.apache.james.jmap.utils.SystemMailboxesProviderImpl; +import org.apache.james.jwt.JwtConfiguration; import org.apache.james.lifecycle.api.Configurable; import org.apache.james.mailbox.MailboxManager; import org.apache.james.mailbox.MailboxManager.SearchCapabilities; @@ -89,6 +90,12 @@ public class JMAPModule extends AbstractModule { .build(); } + @Provides + @Singleton + JwtConfiguration providesJwtConfiguration(JMAPConfiguration jmapConfiguration) { + return new JwtConfiguration(jmapConfiguration.getJwtPublicKeyPem()); + } + private Optional<String> loadPublicKey(FileSystem fileSystem, Optional<String> jwtPublickeyPemUrl) { return jwtPublickeyPemUrl.map(Throwing.function(url -> FileUtils.readFileToString(fileSystem.getFile(url)))); } http://git-wip-us.apache.org/repos/asf/james-project/blob/f1a087fa/server/pom.xml ---------------------------------------------------------------------- diff --git a/server/pom.xml b/server/pom.xml index 4ceaa15..4b7d969 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -109,6 +109,7 @@ <module>protocols/webadmin-integration-test</module> <module>testing</module> + <module>protocols/jwt</module> </modules> <distributionManagement> @@ -807,6 +808,11 @@ </dependency> <dependency> <groupId>org.apache.james</groupId> + <artifactId>james-server-jwt</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.apache.james</groupId> <artifactId>james-server-protocols-lmtp</artifactId> <version>${project.version}</version> </dependency> http://git-wip-us.apache.org/repos/asf/james-project/blob/f1a087fa/server/protocols/jmap/pom.xml ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/pom.xml b/server/protocols/jmap/pom.xml index 152dc79..287e428 100644 --- a/server/protocols/jmap/pom.xml +++ b/server/protocols/jmap/pom.xml @@ -217,6 +217,10 @@ </dependency> <dependency> <groupId>org.apache.james</groupId> + <artifactId>james-server-jwt</artifactId> + </dependency> + <dependency> + <groupId>org.apache.james</groupId> <artifactId>james-server-lifecycle-api</artifactId> </dependency> <dependency> http://git-wip-us.apache.org/repos/asf/james-project/blob/f1a087fa/server/protocols/jmap/src/main/java/org/apache/james/jmap/JWTAuthenticationStrategy.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/JWTAuthenticationStrategy.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/JWTAuthenticationStrategy.java index a7e91be..9f4f609 100644 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/JWTAuthenticationStrategy.java +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/JWTAuthenticationStrategy.java @@ -23,10 +23,10 @@ import java.util.stream.Stream; import javax.inject.Inject; import javax.servlet.http.HttpServletRequest; -import org.apache.james.jmap.crypto.JwtTokenVerifier; import org.apache.james.jmap.exceptions.MailboxSessionCreationException; import org.apache.james.jmap.exceptions.NoValidAuthHeaderException; import org.apache.james.jmap.utils.HeadersAuthenticationExtractor; +import org.apache.james.jwt.JwtTokenVerifier; import org.apache.james.mailbox.MailboxManager; import org.apache.james.mailbox.MailboxSession; import org.apache.james.mailbox.exception.MailboxException; http://git-wip-us.apache.org/repos/asf/james-project/blob/f1a087fa/server/protocols/jmap/src/main/java/org/apache/james/jmap/crypto/JwtTokenVerifier.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/crypto/JwtTokenVerifier.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/crypto/JwtTokenVerifier.java deleted file mode 100644 index dea68af..0000000 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/crypto/JwtTokenVerifier.java +++ /dev/null @@ -1,63 +0,0 @@ -/**************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one * - * or more contributor license agreements. See the NOTICE file * - * distributed with this work for additional information * - * regarding copyright ownership. The ASF licenses this file * - * to you under the Apache License, Version 2.0 (the * - * "License"); you may not use this file except in compliance * - * with the License. You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, * - * software distributed under the License is distributed on an * - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * - * KIND, either express or implied. See the License for the * - * specific language governing permissions and limitations * - * under the License. * - ****************************************************************/ -package org.apache.james.jmap.crypto; - -import javax.inject.Inject; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Strings; - -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Jws; -import io.jsonwebtoken.JwtException; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.MalformedJwtException; - -public class JwtTokenVerifier { - - private final PublicKeyProvider pubKeyProvider; - - @Inject - @VisibleForTesting - JwtTokenVerifier(PublicKeyProvider pubKeyProvider) { - this.pubKeyProvider = pubKeyProvider; - } - - public boolean verify(String token) throws JwtException { - String subject = extractLogin(token); - if (Strings.isNullOrEmpty(subject)) { - throw new MalformedJwtException("'subject' field in token is mandatory"); - } - return true; - } - - public String extractLogin(String token) throws JwtException { - Jws<Claims> jws = parseToken(token); - return jws - .getBody() - .getSubject(); - } - - private Jws<Claims> parseToken(String token) throws JwtException { - return Jwts - .parser() - .setSigningKey(pubKeyProvider.get()) - .parseClaimsJws(token); - } -} http://git-wip-us.apache.org/repos/asf/james-project/blob/f1a087fa/server/protocols/jmap/src/main/java/org/apache/james/jmap/crypto/MissingOrInvalidKeyException.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/crypto/MissingOrInvalidKeyException.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/crypto/MissingOrInvalidKeyException.java deleted file mode 100644 index a98b7e3..0000000 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/crypto/MissingOrInvalidKeyException.java +++ /dev/null @@ -1,22 +0,0 @@ -/**************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one * - * or more contributor license agreements. See the NOTICE file * - * distributed with this work for additional information * - * regarding copyright ownership. The ASF licenses this file * - * to you under the Apache License, Version 2.0 (the * - * "License"); you may not use this file except in compliance * - * with the License. You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, * - * software distributed under the License is distributed on an * - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * - * KIND, either express or implied. See the License for the * - * specific language governing permissions and limitations * - * under the License. * - ****************************************************************/ -package org.apache.james.jmap.crypto; - -public class MissingOrInvalidKeyException extends RuntimeException { -} http://git-wip-us.apache.org/repos/asf/james-project/blob/f1a087fa/server/protocols/jmap/src/main/java/org/apache/james/jmap/crypto/PublicKeyProvider.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/crypto/PublicKeyProvider.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/crypto/PublicKeyProvider.java deleted file mode 100644 index e88ffba..0000000 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/crypto/PublicKeyProvider.java +++ /dev/null @@ -1,44 +0,0 @@ -/**************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one * - * or more contributor license agreements. See the NOTICE file * - * distributed with this work for additional information * - * regarding copyright ownership. The ASF licenses this file * - * to you under the Apache License, Version 2.0 (the * - * "License"); you may not use this file except in compliance * - * with the License. You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, * - * software distributed under the License is distributed on an * - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * - * KIND, either express or implied. See the License for the * - * specific language governing permissions and limitations * - * under the License. * - ****************************************************************/ -package org.apache.james.jmap.crypto; - -import com.google.common.annotations.VisibleForTesting; -import org.apache.james.jmap.JMAPConfiguration; - -import javax.inject.Inject; -import java.security.PublicKey; - -public class PublicKeyProvider { - - private final JMAPConfiguration config; - private final PublicKeyReader reader; - - @Inject - @VisibleForTesting - PublicKeyProvider(JMAPConfiguration config, PublicKeyReader reader) { - this.config = config; - this.reader = reader; - } - - public PublicKey get() throws MissingOrInvalidKeyException { - return reader.fromPEM(config.getJwtPublicKeyPem()) - .orElseThrow(() -> new MissingOrInvalidKeyException()); - } - -} http://git-wip-us.apache.org/repos/asf/james-project/blob/f1a087fa/server/protocols/jmap/src/main/java/org/apache/james/jmap/crypto/PublicKeyReader.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/crypto/PublicKeyReader.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/crypto/PublicKeyReader.java deleted file mode 100644 index b695c26..0000000 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/crypto/PublicKeyReader.java +++ /dev/null @@ -1,56 +0,0 @@ -/**************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one * - * or more contributor license agreements. See the NOTICE file * - * distributed with this work for additional information * - * regarding copyright ownership. The ASF licenses this file * - * to you under the Apache License, Version 2.0 (the * - * "License"); you may not use this file except in compliance * - * with the License. You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, * - * software distributed under the License is distributed on an * - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * - * KIND, either express or implied. See the License for the * - * specific language governing permissions and limitations * - * under the License. * - ****************************************************************/ -package org.apache.james.jmap.crypto; - -import java.io.IOException; -import java.io.StringReader; -import java.security.PublicKey; -import java.util.Optional; - -import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; -import org.bouncycastle.openssl.PEMParser; -import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; -import org.bouncycastle.util.io.pem.PemReader; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class PublicKeyReader { - - private static final Logger LOGGER = LoggerFactory.getLogger(PublicKeyReader.class); - - Optional<PublicKey> fromPEM(Optional<String> pemKey) { - return pemKey - .map(k -> new PEMParser(new PemReader(new StringReader(k)))) - .flatMap(this::publicKeyFrom); - } - - private Optional<PublicKey> publicKeyFrom(PEMParser reader) { - try { - Object readPEM = reader.readObject(); - if (readPEM instanceof SubjectPublicKeyInfo) { - return Optional.of(new JcaPEMKeyConverter().getPublicKey((SubjectPublicKeyInfo) readPEM)); - } - LOGGER.warn("Key is not an instance of SubjectPublicKeyInfo but of " + readPEM); - return Optional.empty(); - } catch (IOException e) { - LOGGER.warn("Error when reading the PEM file", e); - return Optional.empty(); - } - } -} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/james-project/blob/f1a087fa/server/protocols/jmap/src/test/java/org/apache/james/jmap/JWTAuthenticationStrategyTest.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/JWTAuthenticationStrategyTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/JWTAuthenticationStrategyTest.java index 4a60578..530fac4 100644 --- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/JWTAuthenticationStrategyTest.java +++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/JWTAuthenticationStrategyTest.java @@ -29,10 +29,10 @@ import java.util.stream.Stream; import javax.servlet.http.HttpServletRequest; -import org.apache.james.jmap.crypto.JwtTokenVerifier; import org.apache.james.jmap.exceptions.MailboxSessionCreationException; import org.apache.james.jmap.exceptions.NoValidAuthHeaderException; import org.apache.james.jmap.utils.HeadersAuthenticationExtractor; +import org.apache.james.jwt.JwtTokenVerifier; import org.apache.james.mailbox.MailboxManager; import org.apache.james.mailbox.MailboxSession; import org.apache.james.mailbox.exception.MailboxException; http://git-wip-us.apache.org/repos/asf/james-project/blob/f1a087fa/server/protocols/jmap/src/test/java/org/apache/james/jmap/crypto/JwtTokenVerifierTest.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/crypto/JwtTokenVerifierTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/crypto/JwtTokenVerifierTest.java deleted file mode 100644 index e3fa5bd..0000000 --- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/crypto/JwtTokenVerifierTest.java +++ /dev/null @@ -1,124 +0,0 @@ -/**************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one * - * or more contributor license agreements. See the NOTICE file * - * distributed with this work for additional information * - * regarding copyright ownership. The ASF licenses this file * - * to you under the Apache License, Version 2.0 (the * - * "License"); you may not use this file except in compliance * - * with the License. You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, * - * software distributed under the License is distributed on an * - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * - * KIND, either express or implied. See the License for the * - * specific language governing permissions and limitations * - * under the License. * - ****************************************************************/ -package org.apache.james.jmap.crypto; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import java.security.Security; -import java.util.Optional; - -import org.apache.james.jmap.JMAPConfiguration; -import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; - -import io.jsonwebtoken.MalformedJwtException; -import io.jsonwebtoken.SignatureException; - -public class JwtTokenVerifierTest { - - private static final String PUBLIC_PEM_KEY = "-----BEGIN PUBLIC KEY-----\n" + - "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtlChO/nlVP27MpdkG0Bh\n" + - "16XrMRf6M4NeyGa7j5+1UKm42IKUf3lM28oe82MqIIRyvskPc11NuzSor8HmvH8H\n" + - "lhDs5DyJtx2qp35AT0zCqfwlaDnlDc/QDlZv1CoRZGpQk1Inyh6SbZwYpxxwh0fi\n" + - "+d/4RpE3LBVo8wgOaXPylOlHxsDizfkL8QwXItyakBfMO6jWQRrj7/9WDhGf4Hi+\n" + - "GQur1tPGZDl9mvCoRHjFrD5M/yypIPlfMGWFVEvV5jClNMLAQ9bYFuOc7H1fEWw6\n" + - "U1LZUUbJW9/CH45YXz82CYqkrfbnQxqRb2iVbVjs/sHopHd1NTiCfUtwvcYJiBVj\n" + - "kwIDAQAB\n" + - "-----END PUBLIC KEY-----"; - - private static final String VALID_TOKEN = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.T04BTk" + - "LXkJj24coSZkK13RfG25lpvmSl2MJ7N10KpBk9_-95EGYZdog-BDAn3PJzqVw52z-Bwjh4VOj1-j7cURu0cT4jXehhUrlCxS4n7QHZD" + - "N_bsEYGu7KzjWTpTsUiHe-rN7izXVFxDGG1TGwlmBCBnPW-EFCf9ylUsJi0r2BKNdaaPRfMIrHptH1zJBkkUziWpBN1RNLjmvlAUf49" + - "t1Tbv21ZqYM5Ht2vrhJWczFbuC-TD-8zJkXhjTmA1GVgomIX5dx1cH-dZX1wANNmshUJGHgepWlPU-5VIYxPEhb219RMLJIELMY2qN" + - "OR8Q31ydinyqzXvCSzVJOf6T60-w"; - - private JwtTokenVerifier sut; - - @BeforeClass - public static void init() { - Security.addProvider(new BouncyCastleProvider()); - } - - @Before - public void setup() { - PublicKeyProvider pubKeyProvider = new PublicKeyProvider(getJWTConfiguration(), new PublicKeyReader()); - sut = new JwtTokenVerifier(pubKeyProvider); - } - - private JMAPConfiguration getJWTConfiguration() { - - return JMAPConfiguration.builder() - .keystore(".") - .secret(".") - .jwtPublicKeyPem(Optional.ofNullable(PUBLIC_PEM_KEY)) - .build(); - } - - @Test - public void shouldReturnTrueOnValidSignature() { - - assertThat(sut.verify(VALID_TOKEN)).isTrue(); - } - - @Test - public void shouldThrowOnMismatchingSigningKey() { - String invalidToken = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.Pd6t82" + - "tPL3EZdkeYxw_DV2KimE1U2FvuLHmfR_mimJ5US3JFU4J2Gd94O7rwpSTGN1B9h-_lsTebo4ua4xHsTtmczZ9xa8a_kWKaSkqFjNFa" + - "Fp6zcoD6ivCu03SlRqsQzSRHXo6TKbnqOt9D6Y2rNa3C4igSwoS0jUE4BgpXbc0"; - - assertThatThrownBy(() -> sut.verify(invalidToken)) - .isInstanceOf(SignatureException.class); - } - - @Test - public void verifyShouldThrowWhenSubjectIsNull() { - String tokenWithNullSubject = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOm51bGwsIm5hbWUiOiJKb2huIERvZSJ9.EB" + - "_1grWDy_kFelXs3AQeiP13ay4eG_134dWB9XPRSeWsuPs8Mz2UY-VHDxLGD-fAqv-xKXr4QFEnS7iZkdpe0tPLNSwIjqeqkC6KqQln" + - "oC1okqWVWBDOcf7Acp1Jzp_cFTUhL5LkHvZDsyCdq5T9OOVVkzO4A9RrzIUsTrYPtRCBuYJ3ggR33cKpw191PulPGNH70rZqpUfDXe" + - "VPY3q15vWzZH9O9IJzB2KdHRMPxl2luRjzDbh4DLp56NhZuLX_2a9UAlmbV8MQX4Z_04ybhAYrcBfxR3MgJyr0jlxSibqSbXrkXuo-" + - "PyybfZCIhK_qXUlO5OS6sO7AQhKZO9p0MQ"; - - assertThatThrownBy(() -> sut.verify(tokenWithNullSubject)) - .isInstanceOf(MalformedJwtException.class) - .hasMessage("'subject' field in token is mandatory"); - } - - @Test - public void verifyShouldThrowWhenEmptySubject() { - String tokenWithEmptySubject = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIiLCJuYW1lIjoiSm9obiBEb2UifQ.UdY" + - "s2PPzFCegUYspoDCnlJR_bJm8_z1InOv4v3tq8SJETQUarOXlhb_n6y6ujVvmJiSx9dI24Hc3Czi3RGUOXbnBDj1WPfd0aVSiUSqZr" + - "MCHBt5vjCYqAseDaP3w4aiiFb6EV3tteJFeBLZx8XlKPYxlzRLLUADDyDSQvrFBBPxfsvCETZovKdo9ofIN64o-yx23ss63yE6oIOd" + - "zJZ1Id40KSR2d7l3kIQJPLKUWJDnro5RAh4DOGOWNSq0JSbMhk7Zn3cXIBUpv3R8p79tui1UQpzwHMC0e6OSuWEDNQHtq-Cz85u8GG" + - "sUSbogmgObA_BimNtUq_Q1p0SGtIYBXmQ"; - - assertThatThrownBy(() -> sut.verify(tokenWithEmptySubject)) - .isInstanceOf(MalformedJwtException.class) - .hasMessage("'subject' field in token is mandatory"); - } - - @Test - public void shouldReturnUserLoginFromValidToken() { - - assertThat(sut.extractLogin(VALID_TOKEN)).isEqualTo("1234567890"); - } - -} http://git-wip-us.apache.org/repos/asf/james-project/blob/f1a087fa/server/protocols/jmap/src/test/java/org/apache/james/jmap/crypto/PublicKeyProviderTest.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/crypto/PublicKeyProviderTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/crypto/PublicKeyProviderTest.java deleted file mode 100644 index 5db7d7b..0000000 --- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/crypto/PublicKeyProviderTest.java +++ /dev/null @@ -1,74 +0,0 @@ -/**************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one * - * or more contributor license agreements. See the NOTICE file * - * distributed with this work for additional information * - * regarding copyright ownership. The ASF licenses this file * - * to you under the Apache License, Version 2.0 (the * - * "License"); you may not use this file except in compliance * - * with the License. You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, * - * software distributed under the License is distributed on an * - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * - * KIND, either express or implied. See the License for the * - * specific language governing permissions and limitations * - * under the License. * - ****************************************************************/ -package org.apache.james.jmap.crypto; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import java.security.Security; -import java.security.interfaces.RSAPublicKey; -import java.util.Optional; - -import org.apache.james.jmap.JMAPConfiguration; -import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.junit.BeforeClass; -import org.junit.Test; - -public class PublicKeyProviderTest { - - private static final String PUBLIC_PEM_KEY = "-----BEGIN PUBLIC KEY-----\n" + - "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtlChO/nlVP27MpdkG0Bh\n" + - "16XrMRf6M4NeyGa7j5+1UKm42IKUf3lM28oe82MqIIRyvskPc11NuzSor8HmvH8H\n" + - "lhDs5DyJtx2qp35AT0zCqfwlaDnlDc/QDlZv1CoRZGpQk1Inyh6SbZwYpxxwh0fi\n" + - "+d/4RpE3LBVo8wgOaXPylOlHxsDizfkL8QwXItyakBfMO6jWQRrj7/9WDhGf4Hi+\n" + - "GQur1tPGZDl9mvCoRHjFrD5M/yypIPlfMGWFVEvV5jClNMLAQ9bYFuOc7H1fEWw6\n" + - "U1LZUUbJW9/CH45YXz82CYqkrfbnQxqRb2iVbVjs/sHopHd1NTiCfUtwvcYJiBVj\n" + - "kwIDAQAB\n" + - "-----END PUBLIC KEY-----"; - - @BeforeClass - public static void init() { - Security.addProvider(new BouncyCastleProvider()); - } - - @Test - public void getShouldNotThrowWhenPEMKeyProvided() { - - JMAPConfiguration configWithPEMKey = JMAPConfiguration.builder() - .jwtPublicKeyPem(Optional.ofNullable(PUBLIC_PEM_KEY)) - .keystore(".").secret(".") - .build(); - - PublicKeyProvider sut = new PublicKeyProvider(configWithPEMKey, new PublicKeyReader()); - - assertThat(sut.get()).isInstanceOf(RSAPublicKey.class); - } - - @Test - public void getShouldThrowWhenPEMKeyNotProvided() { - JMAPConfiguration configWithPEMKey = JMAPConfiguration.builder() - .jwtPublicKeyPem(Optional.ofNullable("")) - .keystore(" ").secret(" ") - .build(); - - PublicKeyProvider sut = new PublicKeyProvider(configWithPEMKey, new PublicKeyReader()); - - assertThatThrownBy(() -> sut.get()).isExactlyInstanceOf(MissingOrInvalidKeyException.class); - } -} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/james-project/blob/f1a087fa/server/protocols/jmap/src/test/java/org/apache/james/jmap/crypto/PublicKeyReaderTest.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/crypto/PublicKeyReaderTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/crypto/PublicKeyReaderTest.java deleted file mode 100644 index 2765282..0000000 --- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/crypto/PublicKeyReaderTest.java +++ /dev/null @@ -1,62 +0,0 @@ -/**************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one * - * or more contributor license agreements. See the NOTICE file * - * distributed with this work for additional information * - * regarding copyright ownership. The ASF licenses this file * - * to you under the Apache License, Version 2.0 (the * - * "License"); you may not use this file except in compliance * - * with the License. You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, * - * software distributed under the License is distributed on an * - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * - * KIND, either express or implied. See the License for the * - * specific language governing permissions and limitations * - * under the License. * - ****************************************************************/ - -package org.apache.james.jmap.crypto; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.security.Security; -import java.util.Optional; - -import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.junit.BeforeClass; -import org.junit.Test; - -public class PublicKeyReaderTest { - - private static final String PUBLIC_PEM_KEY = "-----BEGIN PUBLIC KEY-----\n" + - "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtlChO/nlVP27MpdkG0Bh\n" + - "16XrMRf6M4NeyGa7j5+1UKm42IKUf3lM28oe82MqIIRyvskPc11NuzSor8HmvH8H\n" + - "lhDs5DyJtx2qp35AT0zCqfwlaDnlDc/QDlZv1CoRZGpQk1Inyh6SbZwYpxxwh0fi\n" + - "+d/4RpE3LBVo8wgOaXPylOlHxsDizfkL8QwXItyakBfMO6jWQRrj7/9WDhGf4Hi+\n" + - "GQur1tPGZDl9mvCoRHjFrD5M/yypIPlfMGWFVEvV5jClNMLAQ9bYFuOc7H1fEWw6\n" + - "U1LZUUbJW9/CH45YXz82CYqkrfbnQxqRb2iVbVjs/sHopHd1NTiCfUtwvcYJiBVj\n" + - "kwIDAQAB\n" + - "-----END PUBLIC KEY-----"; - - @BeforeClass - public static void init() { - Security.addProvider(new BouncyCastleProvider()); - } - - @Test - public void fromPEMShouldReturnEmptyWhenEmptyProvided() { - assertThat(new PublicKeyReader().fromPEM(Optional.empty())).isEmpty(); - } - - @Test - public void fromPEMShouldReturnEmptyWhenInvalidPEMKey() { - assertThat(new PublicKeyReader().fromPEM(Optional.of("blabla"))).isEmpty(); - } - - @Test - public void fromPEMShouldReturnRSAPublicKeyWhenValidPEMKey() { - assertThat(new PublicKeyReader().fromPEM(Optional.of(PUBLIC_PEM_KEY))).isPresent(); - } -} http://git-wip-us.apache.org/repos/asf/james-project/blob/f1a087fa/server/protocols/jwt/pom.xml ---------------------------------------------------------------------- diff --git a/server/protocols/jwt/pom.xml b/server/protocols/jwt/pom.xml new file mode 100644 index 0000000..85d17b9 --- /dev/null +++ b/server/protocols/jwt/pom.xml @@ -0,0 +1,245 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <parent> + <artifactId>james-server</artifactId> + <groupId>org.apache.james</groupId> + <version>3.0.0-beta6-SNAPSHOT</version> + <relativePath>../../pom.xml</relativePath> + </parent> + <modelVersion>4.0.0</modelVersion> + + <artifactId>james-server-jwt</artifactId> + <packaging>jar</packaging> + + <name>Apache James :: Server :: JWT</name> + + <profiles> + <profile> + <id>noTest</id> + <activation> + <os> + <family>windows</family> + </os> + </activation> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <skipTests>true</skipTests> + </configuration> + </plugin> + </plugins> + </build> + </profile> + <profile> + <id>disable-build-for-older-jdk</id> + <activation> + <jdk>(,1.8)</jdk> + </activation> + <build> + <plugins> + <plugin> + <artifactId>maven-jar-plugin</artifactId> + <executions> + <execution> + <id>default-jar</id> + <phase>none</phase> + </execution> + <execution> + <id>jar</id> + <phase>none</phase> + </execution> + <execution> + <id>test-jar</id> + <phase>none</phase> + </execution> + </executions> + </plugin> + <plugin> + <artifactId>maven-compiler-plugin</artifactId> + <executions> + <execution> + <id>default-compile</id> + <phase>none</phase> + </execution> + <execution> + <id>default-testCompile</id> + <phase>none</phase> + </execution> + </executions> + </plugin> + <plugin> + <artifactId>maven-surefire-plugin</artifactId> + <executions> + <execution> + <id>default-test</id> + <phase>none</phase> + </execution> + </executions> + </plugin> + <plugin> + <artifactId>maven-source-plugin</artifactId> + <executions> + <execution> + <id>attach-sources</id> + <phase>none</phase> + </execution> + </executions> + </plugin> + <plugin> + <artifactId>maven-install-plugin</artifactId> + <executions> + <execution> + <id>default-install</id> + <phase>none</phase> + </execution> + </executions> + </plugin> + <plugin> + <artifactId>maven-resources-plugin</artifactId> + <executions> + <execution> + <id>default-resources</id> + <phase>none</phase> + </execution> + <execution> + <id>default-testResources</id> + <phase>none</phase> + </execution> + </executions> + </plugin> + <plugin> + <artifactId>maven-site-plugin</artifactId> + <executions> + <execution> + <id>attach-descriptor</id> + <phase>none</phase> + </execution> + </executions> + </plugin> + </plugins> + </build> + </profile> + <profile> + <id>build-for-jdk-8</id> + <activation> + <jdk>[1.8,)</jdk> + </activation> + <dependencies> + <dependency> + <groupId>javax.inject</groupId> + <artifactId>javax.inject</artifactId> + </dependency> + <dependency> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + </dependency> + <dependency> + <groupId>io.jsonwebtoken</groupId> + <artifactId>jjwt</artifactId> + </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.assertj</groupId> + <artifactId>assertj-core</artifactId> + <version>${assertj-3.version}</version> + </dependency> + <dependency> + <groupId>org.bouncycastle</groupId> + <artifactId>bcpkix-jdk15on</artifactId> + </dependency> + <dependency> + <groupId>org.bouncycastle</groupId> + <artifactId>bcprov-jdk15on</artifactId> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + </dependency> + </dependencies> + <build> + <plugins> + <plugin> + <artifactId>maven-assembly-plugin</artifactId> + <configuration> + <archive> + <manifest> + <mainClass>fully.qualified.MainClass</mainClass> + </manifest> + </archive> + <descriptorRefs> + <descriptorRef>jar-with-dependencies</descriptorRef> + </descriptorRefs> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <configuration> + <source>1.8</source> + <target>1.8</target> + </configuration> + </plugin> + </plugins> + </build> + </profile> + <profile> + <id>animal-sniffer-java-8</id> + <activation> + <jdk>[1.8,)</jdk> + </activation> + <build> + <plugins> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>animal-sniffer-maven-plugin</artifactId> + <configuration> + <signature> + <groupId>org.codehaus.mojo.signature</groupId> + <artifactId>java18</artifactId> + <version>1.0</version> + </signature> + </configuration> + <executions> + <execution> + <id>check_java_8</id> + <phase>test</phase> + <goals> + <goal>check</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> + </profile> + </profiles> + + +</project> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/james-project/blob/f1a087fa/server/protocols/jwt/src/main/java/org/apache/james/jwt/JwtConfiguration.java ---------------------------------------------------------------------- diff --git a/server/protocols/jwt/src/main/java/org/apache/james/jwt/JwtConfiguration.java b/server/protocols/jwt/src/main/java/org/apache/james/jwt/JwtConfiguration.java new file mode 100644 index 0000000..3cfcfef --- /dev/null +++ b/server/protocols/jwt/src/main/java/org/apache/james/jwt/JwtConfiguration.java @@ -0,0 +1,34 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.jwt; + +import java.util.Optional; + +public class JwtConfiguration { + private final Optional<String> jwtPublicKeyPem; + + public JwtConfiguration(Optional<String> jwtPublicKeyPem) { + this.jwtPublicKeyPem = jwtPublicKeyPem; + } + + public Optional<String> getJwtPublicKeyPem() { + return jwtPublicKeyPem; + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/f1a087fa/server/protocols/jwt/src/main/java/org/apache/james/jwt/JwtTokenVerifier.java ---------------------------------------------------------------------- diff --git a/server/protocols/jwt/src/main/java/org/apache/james/jwt/JwtTokenVerifier.java b/server/protocols/jwt/src/main/java/org/apache/james/jwt/JwtTokenVerifier.java new file mode 100644 index 0000000..661302f --- /dev/null +++ b/server/protocols/jwt/src/main/java/org/apache/james/jwt/JwtTokenVerifier.java @@ -0,0 +1,63 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.jwt; + +import javax.inject.Inject; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Strings; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; +import io.jsonwebtoken.JwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.MalformedJwtException; + +public class JwtTokenVerifier { + + private final PublicKeyProvider pubKeyProvider; + + @Inject + @VisibleForTesting + JwtTokenVerifier(PublicKeyProvider pubKeyProvider) { + this.pubKeyProvider = pubKeyProvider; + } + + public boolean verify(String token) throws JwtException { + String subject = extractLogin(token); + if (Strings.isNullOrEmpty(subject)) { + throw new MalformedJwtException("'subject' field in token is mandatory"); + } + return true; + } + + public String extractLogin(String token) throws JwtException { + Jws<Claims> jws = parseToken(token); + return jws + .getBody() + .getSubject(); + } + + private Jws<Claims> parseToken(String token) throws JwtException { + return Jwts + .parser() + .setSigningKey(pubKeyProvider.get()) + .parseClaimsJws(token); + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/f1a087fa/server/protocols/jwt/src/main/java/org/apache/james/jwt/MissingOrInvalidKeyException.java ---------------------------------------------------------------------- diff --git a/server/protocols/jwt/src/main/java/org/apache/james/jwt/MissingOrInvalidKeyException.java b/server/protocols/jwt/src/main/java/org/apache/james/jwt/MissingOrInvalidKeyException.java new file mode 100644 index 0000000..b61de91 --- /dev/null +++ b/server/protocols/jwt/src/main/java/org/apache/james/jwt/MissingOrInvalidKeyException.java @@ -0,0 +1,22 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.jwt; + +public class MissingOrInvalidKeyException extends RuntimeException { +} http://git-wip-us.apache.org/repos/asf/james-project/blob/f1a087fa/server/protocols/jwt/src/main/java/org/apache/james/jwt/PublicKeyProvider.java ---------------------------------------------------------------------- diff --git a/server/protocols/jwt/src/main/java/org/apache/james/jwt/PublicKeyProvider.java b/server/protocols/jwt/src/main/java/org/apache/james/jwt/PublicKeyProvider.java new file mode 100644 index 0000000..9ce7792 --- /dev/null +++ b/server/protocols/jwt/src/main/java/org/apache/james/jwt/PublicKeyProvider.java @@ -0,0 +1,44 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.jwt; + +import java.security.PublicKey; + +import javax.inject.Inject; + +import com.google.common.annotations.VisibleForTesting; + +public class PublicKeyProvider { + + private final JwtConfiguration jwtConfiguration; + private final PublicKeyReader reader; + + @Inject + @VisibleForTesting + PublicKeyProvider(JwtConfiguration jwtConfiguration, PublicKeyReader reader) { + this.jwtConfiguration = jwtConfiguration; + this.reader = reader; + } + + public PublicKey get() throws MissingOrInvalidKeyException { + return reader.fromPEM(jwtConfiguration.getJwtPublicKeyPem()) + .orElseThrow(MissingOrInvalidKeyException::new); + } + +} http://git-wip-us.apache.org/repos/asf/james-project/blob/f1a087fa/server/protocols/jwt/src/main/java/org/apache/james/jwt/PublicKeyReader.java ---------------------------------------------------------------------- diff --git a/server/protocols/jwt/src/main/java/org/apache/james/jwt/PublicKeyReader.java b/server/protocols/jwt/src/main/java/org/apache/james/jwt/PublicKeyReader.java new file mode 100644 index 0000000..779fa1a --- /dev/null +++ b/server/protocols/jwt/src/main/java/org/apache/james/jwt/PublicKeyReader.java @@ -0,0 +1,56 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.jwt; + +import java.io.IOException; +import java.io.StringReader; +import java.security.PublicKey; +import java.util.Optional; + +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.openssl.PEMParser; +import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; +import org.bouncycastle.util.io.pem.PemReader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class PublicKeyReader { + + private static final Logger LOGGER = LoggerFactory.getLogger(PublicKeyReader.class); + + Optional<PublicKey> fromPEM(Optional<String> pemKey) { + return pemKey + .map(k -> new PEMParser(new PemReader(new StringReader(k)))) + .flatMap(this::publicKeyFrom); + } + + private Optional<PublicKey> publicKeyFrom(PEMParser reader) { + try { + Object readPEM = reader.readObject(); + if (readPEM instanceof SubjectPublicKeyInfo) { + return Optional.of(new JcaPEMKeyConverter().getPublicKey((SubjectPublicKeyInfo) readPEM)); + } + LOGGER.warn("Key is not an instance of SubjectPublicKeyInfo but of " + readPEM); + return Optional.empty(); + } catch (IOException e) { + LOGGER.warn("Error when reading the PEM file", e); + return Optional.empty(); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/james-project/blob/f1a087fa/server/protocols/jwt/src/test/java/org/apache/james/jwt/JwtTokenVerifierTest.java ---------------------------------------------------------------------- diff --git a/server/protocols/jwt/src/test/java/org/apache/james/jwt/JwtTokenVerifierTest.java b/server/protocols/jwt/src/test/java/org/apache/james/jwt/JwtTokenVerifierTest.java new file mode 100644 index 0000000..ac5cc94 --- /dev/null +++ b/server/protocols/jwt/src/test/java/org/apache/james/jwt/JwtTokenVerifierTest.java @@ -0,0 +1,119 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.jwt; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.security.Security; +import java.util.Optional; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import io.jsonwebtoken.MalformedJwtException; +import io.jsonwebtoken.SignatureException; + +public class JwtTokenVerifierTest { + + private static final String PUBLIC_PEM_KEY = "-----BEGIN PUBLIC KEY-----\n" + + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtlChO/nlVP27MpdkG0Bh\n" + + "16XrMRf6M4NeyGa7j5+1UKm42IKUf3lM28oe82MqIIRyvskPc11NuzSor8HmvH8H\n" + + "lhDs5DyJtx2qp35AT0zCqfwlaDnlDc/QDlZv1CoRZGpQk1Inyh6SbZwYpxxwh0fi\n" + + "+d/4RpE3LBVo8wgOaXPylOlHxsDizfkL8QwXItyakBfMO6jWQRrj7/9WDhGf4Hi+\n" + + "GQur1tPGZDl9mvCoRHjFrD5M/yypIPlfMGWFVEvV5jClNMLAQ9bYFuOc7H1fEWw6\n" + + "U1LZUUbJW9/CH45YXz82CYqkrfbnQxqRb2iVbVjs/sHopHd1NTiCfUtwvcYJiBVj\n" + + "kwIDAQAB\n" + + "-----END PUBLIC KEY-----"; + + private static final String VALID_TOKEN = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.T04BTk" + + "LXkJj24coSZkK13RfG25lpvmSl2MJ7N10KpBk9_-95EGYZdog-BDAn3PJzqVw52z-Bwjh4VOj1-j7cURu0cT4jXehhUrlCxS4n7QHZD" + + "N_bsEYGu7KzjWTpTsUiHe-rN7izXVFxDGG1TGwlmBCBnPW-EFCf9ylUsJi0r2BKNdaaPRfMIrHptH1zJBkkUziWpBN1RNLjmvlAUf49" + + "t1Tbv21ZqYM5Ht2vrhJWczFbuC-TD-8zJkXhjTmA1GVgomIX5dx1cH-dZX1wANNmshUJGHgepWlPU-5VIYxPEhb219RMLJIELMY2qN" + + "OR8Q31ydinyqzXvCSzVJOf6T60-w"; + + private JwtTokenVerifier sut; + + @BeforeClass + public static void init() { + Security.addProvider(new BouncyCastleProvider()); + } + + @Before + public void setup() { + PublicKeyProvider pubKeyProvider = new PublicKeyProvider(getJWTConfiguration(), new PublicKeyReader()); + sut = new JwtTokenVerifier(pubKeyProvider); + } + + private JwtConfiguration getJWTConfiguration() { + + return new JwtConfiguration(Optional.of(PUBLIC_PEM_KEY)); + } + + @Test + public void shouldReturnTrueOnValidSignature() { + + assertThat(sut.verify(VALID_TOKEN)).isTrue(); + } + + @Test + public void shouldThrowOnMismatchingSigningKey() { + String invalidToken = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.Pd6t82" + + "tPL3EZdkeYxw_DV2KimE1U2FvuLHmfR_mimJ5US3JFU4J2Gd94O7rwpSTGN1B9h-_lsTebo4ua4xHsTtmczZ9xa8a_kWKaSkqFjNFa" + + "Fp6zcoD6ivCu03SlRqsQzSRHXo6TKbnqOt9D6Y2rNa3C4igSwoS0jUE4BgpXbc0"; + + assertThatThrownBy(() -> sut.verify(invalidToken)) + .isInstanceOf(SignatureException.class); + } + + @Test + public void verifyShouldThrowWhenSubjectIsNull() { + String tokenWithNullSubject = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOm51bGwsIm5hbWUiOiJKb2huIERvZSJ9.EB" + + "_1grWDy_kFelXs3AQeiP13ay4eG_134dWB9XPRSeWsuPs8Mz2UY-VHDxLGD-fAqv-xKXr4QFEnS7iZkdpe0tPLNSwIjqeqkC6KqQln" + + "oC1okqWVWBDOcf7Acp1Jzp_cFTUhL5LkHvZDsyCdq5T9OOVVkzO4A9RrzIUsTrYPtRCBuYJ3ggR33cKpw191PulPGNH70rZqpUfDXe" + + "VPY3q15vWzZH9O9IJzB2KdHRMPxl2luRjzDbh4DLp56NhZuLX_2a9UAlmbV8MQX4Z_04ybhAYrcBfxR3MgJyr0jlxSibqSbXrkXuo-" + + "PyybfZCIhK_qXUlO5OS6sO7AQhKZO9p0MQ"; + + assertThatThrownBy(() -> sut.verify(tokenWithNullSubject)) + .isInstanceOf(MalformedJwtException.class) + .hasMessage("'subject' field in token is mandatory"); + } + + @Test + public void verifyShouldThrowWhenEmptySubject() { + String tokenWithEmptySubject = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIiLCJuYW1lIjoiSm9obiBEb2UifQ.UdY" + + "s2PPzFCegUYspoDCnlJR_bJm8_z1InOv4v3tq8SJETQUarOXlhb_n6y6ujVvmJiSx9dI24Hc3Czi3RGUOXbnBDj1WPfd0aVSiUSqZr" + + "MCHBt5vjCYqAseDaP3w4aiiFb6EV3tteJFeBLZx8XlKPYxlzRLLUADDyDSQvrFBBPxfsvCETZovKdo9ofIN64o-yx23ss63yE6oIOd" + + "zJZ1Id40KSR2d7l3kIQJPLKUWJDnro5RAh4DOGOWNSq0JSbMhk7Zn3cXIBUpv3R8p79tui1UQpzwHMC0e6OSuWEDNQHtq-Cz85u8GG" + + "sUSbogmgObA_BimNtUq_Q1p0SGtIYBXmQ"; + + assertThatThrownBy(() -> sut.verify(tokenWithEmptySubject)) + .isInstanceOf(MalformedJwtException.class) + .hasMessage("'subject' field in token is mandatory"); + } + + @Test + public void shouldReturnUserLoginFromValidToken() { + + assertThat(sut.extractLogin(VALID_TOKEN)).isEqualTo("1234567890"); + } + +} http://git-wip-us.apache.org/repos/asf/james-project/blob/f1a087fa/server/protocols/jwt/src/test/java/org/apache/james/jwt/PublicKeyProviderTest.java ---------------------------------------------------------------------- diff --git a/server/protocols/jwt/src/test/java/org/apache/james/jwt/PublicKeyProviderTest.java b/server/protocols/jwt/src/test/java/org/apache/james/jwt/PublicKeyProviderTest.java new file mode 100644 index 0000000..feb4b66 --- /dev/null +++ b/server/protocols/jwt/src/test/java/org/apache/james/jwt/PublicKeyProviderTest.java @@ -0,0 +1,67 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.jwt; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.security.Security; +import java.security.interfaces.RSAPublicKey; +import java.util.Optional; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.junit.BeforeClass; +import org.junit.Test; + +public class PublicKeyProviderTest { + + private static final String PUBLIC_PEM_KEY = "-----BEGIN PUBLIC KEY-----\n" + + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtlChO/nlVP27MpdkG0Bh\n" + + "16XrMRf6M4NeyGa7j5+1UKm42IKUf3lM28oe82MqIIRyvskPc11NuzSor8HmvH8H\n" + + "lhDs5DyJtx2qp35AT0zCqfwlaDnlDc/QDlZv1CoRZGpQk1Inyh6SbZwYpxxwh0fi\n" + + "+d/4RpE3LBVo8wgOaXPylOlHxsDizfkL8QwXItyakBfMO6jWQRrj7/9WDhGf4Hi+\n" + + "GQur1tPGZDl9mvCoRHjFrD5M/yypIPlfMGWFVEvV5jClNMLAQ9bYFuOc7H1fEWw6\n" + + "U1LZUUbJW9/CH45YXz82CYqkrfbnQxqRb2iVbVjs/sHopHd1NTiCfUtwvcYJiBVj\n" + + "kwIDAQAB\n" + + "-----END PUBLIC KEY-----"; + + @BeforeClass + public static void init() { + Security.addProvider(new BouncyCastleProvider()); + } + + @Test + public void getShouldNotThrowWhenPEMKeyProvided() { + + JwtConfiguration configWithPEMKey = new JwtConfiguration(Optional.of(PUBLIC_PEM_KEY)); + + PublicKeyProvider sut = new PublicKeyProvider(configWithPEMKey, new PublicKeyReader()); + + assertThat(sut.get()).isInstanceOf(RSAPublicKey.class); + } + + @Test + public void getShouldThrowWhenPEMKeyNotProvided() { + JwtConfiguration configWithPEMKey = new JwtConfiguration(Optional.of("")); + + PublicKeyProvider sut = new PublicKeyProvider(configWithPEMKey, new PublicKeyReader()); + + assertThatThrownBy(() -> sut.get()).isExactlyInstanceOf(MissingOrInvalidKeyException.class); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/james-project/blob/f1a087fa/server/protocols/jwt/src/test/java/org/apache/james/jwt/PublicKeyReaderTest.java ---------------------------------------------------------------------- diff --git a/server/protocols/jwt/src/test/java/org/apache/james/jwt/PublicKeyReaderTest.java b/server/protocols/jwt/src/test/java/org/apache/james/jwt/PublicKeyReaderTest.java new file mode 100644 index 0000000..1b850d0 --- /dev/null +++ b/server/protocols/jwt/src/test/java/org/apache/james/jwt/PublicKeyReaderTest.java @@ -0,0 +1,62 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.jwt; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.Security; +import java.util.Optional; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.junit.BeforeClass; +import org.junit.Test; + +public class PublicKeyReaderTest { + + private static final String PUBLIC_PEM_KEY = "-----BEGIN PUBLIC KEY-----\n" + + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtlChO/nlVP27MpdkG0Bh\n" + + "16XrMRf6M4NeyGa7j5+1UKm42IKUf3lM28oe82MqIIRyvskPc11NuzSor8HmvH8H\n" + + "lhDs5DyJtx2qp35AT0zCqfwlaDnlDc/QDlZv1CoRZGpQk1Inyh6SbZwYpxxwh0fi\n" + + "+d/4RpE3LBVo8wgOaXPylOlHxsDizfkL8QwXItyakBfMO6jWQRrj7/9WDhGf4Hi+\n" + + "GQur1tPGZDl9mvCoRHjFrD5M/yypIPlfMGWFVEvV5jClNMLAQ9bYFuOc7H1fEWw6\n" + + "U1LZUUbJW9/CH45YXz82CYqkrfbnQxqRb2iVbVjs/sHopHd1NTiCfUtwvcYJiBVj\n" + + "kwIDAQAB\n" + + "-----END PUBLIC KEY-----"; + + @BeforeClass + public static void init() { + Security.addProvider(new BouncyCastleProvider()); + } + + @Test + public void fromPEMShouldReturnEmptyWhenEmptyProvided() { + assertThat(new PublicKeyReader().fromPEM(Optional.empty())).isEmpty(); + } + + @Test + public void fromPEMShouldReturnEmptyWhenInvalidPEMKey() { + assertThat(new PublicKeyReader().fromPEM(Optional.of("blabla"))).isEmpty(); + } + + @Test + public void fromPEMShouldReturnRSAPublicKeyWhenValidPEMKey() { + assertThat(new PublicKeyReader().fromPEM(Optional.of(PUBLIC_PEM_KEY))).isPresent(); + } +} --------------------------------------------------------------------- To unsubscribe, e-mail: server-dev-unsubscr...@james.apache.org For additional commands, e-mail: server-dev-h...@james.apache.org