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

Reply via email to