This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit c3876cf57482e186b47a3f300237c0436638cb13
Author: Tran Tien Duc <[email protected]>
AuthorDate: Thu Jun 6 10:21:20 2019 +0700

    JAMES-2146 a new component dedicated to load java security keys from 
configuration
    
    And later, use it in new KeyStore StartUpCheck
---
 .../org/apache/james/jmap/JMAPCommonModule.java    |  2 +
 .../apache/james/jmap/crypto/AsymmetricKeys.java   | 42 ++++++++++
 .../james/jmap/crypto/JamesSignatureHandler.java   |  2 +-
 ...ignatureHandler.java => SecurityKeyLoader.java} | 61 ++------------
 .../james/jmap/crypto/ClassLoaderFileSystem.java   | 45 ++++++++++
 .../james/jmap/crypto/SecurityKeyLoaderTest.java   | 96 ++++++++++++++++++++++
 6 files changed, 194 insertions(+), 54 deletions(-)

diff --git 
a/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPCommonModule.java
 
b/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPCommonModule.java
index c562d60..9a44d3a 100644
--- 
a/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPCommonModule.java
+++ 
b/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPCommonModule.java
@@ -27,6 +27,7 @@ import org.apache.james.jmap.api.SimpleTokenManager;
 import org.apache.james.jmap.api.access.AccessTokenRepository;
 import org.apache.james.jmap.crypto.AccessTokenManagerImpl;
 import org.apache.james.jmap.crypto.JamesSignatureHandler;
+import org.apache.james.jmap.crypto.SecurityKeyLoader;
 import org.apache.james.jmap.crypto.SignatureHandler;
 import org.apache.james.jmap.crypto.SignedTokenFactory;
 import org.apache.james.jmap.crypto.SignedTokenManager;
@@ -64,6 +65,7 @@ public class JMAPCommonModule extends AbstractModule {
         bind(MessagePreviewGenerator.class).in(Scopes.SINGLETON);
         bind(MessageContentExtractor.class).in(Scopes.SINGLETON);
         bind(HeadersAuthenticationExtractor.class).in(Scopes.SINGLETON);
+        bind(SecurityKeyLoader.class).in(Scopes.SINGLETON);
 
         bind(SignatureHandler.class).to(JamesSignatureHandler.class);
         
bind(ZonedDateTimeProvider.class).to(DefaultZonedDateTimeProvider.class);
diff --git 
a/server/protocols/jmap/src/main/java/org/apache/james/jmap/crypto/AsymmetricKeys.java
 
b/server/protocols/jmap/src/main/java/org/apache/james/jmap/crypto/AsymmetricKeys.java
new file mode 100644
index 0000000..301fd4e
--- /dev/null
+++ 
b/server/protocols/jmap/src/main/java/org/apache/james/jmap/crypto/AsymmetricKeys.java
@@ -0,0 +1,42 @@
+/****************************************************************
+ * 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.security.PrivateKey;
+import java.security.PublicKey;
+
+class AsymmetricKeys {
+
+    private final PrivateKey privateKey;
+    private final PublicKey publicKey;
+
+    AsymmetricKeys(PrivateKey privateKey, PublicKey publicKey) {
+        this.privateKey = privateKey;
+        this.publicKey = publicKey;
+    }
+
+    PrivateKey getPrivateKey() {
+        return privateKey;
+    }
+
+    PublicKey getPublicKey() {
+        return publicKey;
+    }
+}
diff --git 
a/server/protocols/jmap/src/main/java/org/apache/james/jmap/crypto/JamesSignatureHandler.java
 
b/server/protocols/jmap/src/main/java/org/apache/james/jmap/crypto/JamesSignatureHandler.java
index 37d3456..82ee210 100644
--- 
a/server/protocols/jmap/src/main/java/org/apache/james/jmap/crypto/JamesSignatureHandler.java
+++ 
b/server/protocols/jmap/src/main/java/org/apache/james/jmap/crypto/JamesSignatureHandler.java
@@ -50,7 +50,7 @@ public class JamesSignatureHandler implements 
SignatureHandler {
     public static final String ALIAS = "james";
     public static final String ALGORITHM = "SHA1withRSA";
     public static final String JKS = "JKS";
-    
+
     private final FileSystem fileSystem;
     private final JMAPConfiguration jmapConfiguration;
 
diff --git 
a/server/protocols/jmap/src/main/java/org/apache/james/jmap/crypto/JamesSignatureHandler.java
 
b/server/protocols/jmap/src/main/java/org/apache/james/jmap/crypto/SecurityKeyLoader.java
similarity index 53%
copy from 
server/protocols/jmap/src/main/java/org/apache/james/jmap/crypto/JamesSignatureHandler.java
copy to 
server/protocols/jmap/src/main/java/org/apache/james/jmap/crypto/SecurityKeyLoader.java
index 37d3456..0bb359c 100644
--- 
a/server/protocols/jmap/src/main/java/org/apache/james/jmap/crypto/JamesSignatureHandler.java
+++ 
b/server/protocols/jmap/src/main/java/org/apache/james/jmap/crypto/SecurityKeyLoader.java
@@ -20,52 +20,37 @@
 package org.apache.james.jmap.crypto;
 
 import java.io.InputStream;
-import java.security.InvalidKeyException;
 import java.security.Key;
 import java.security.KeyStore;
 import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
 import java.security.PrivateKey;
 import java.security.PublicKey;
-import java.security.Signature;
-import java.security.SignatureException;
 import java.security.cert.Certificate;
-import java.util.Base64;
 import java.util.Optional;
 
 import javax.inject.Inject;
 
 import org.apache.james.filesystem.api.FileSystem;
 import org.apache.james.jmap.JMAPConfiguration;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Preconditions;
 
-public class JamesSignatureHandler implements SignatureHandler {
+public class SecurityKeyLoader {
 
-    private static final Logger LOGGER = 
LoggerFactory.getLogger(JamesSignatureHandler.class);
+    private static final String ALIAS = "james";
+    private static final String JKS = "JKS";
 
-    public static final String ALIAS = "james";
-    public static final String ALGORITHM = "SHA1withRSA";
-    public static final String JKS = "JKS";
-    
     private final FileSystem fileSystem;
     private final JMAPConfiguration jmapConfiguration;
 
-    private PrivateKey privateKey;
-    private PublicKey publicKey;
-
-
+    @VisibleForTesting
     @Inject
-    @VisibleForTesting JamesSignatureHandler(FileSystem fileSystem, 
JMAPConfiguration jmapConfiguration) {
+    SecurityKeyLoader(FileSystem fileSystem, JMAPConfiguration 
jmapConfiguration) {
         this.fileSystem = fileSystem;
         this.jmapConfiguration = jmapConfiguration;
     }
 
-    @Override
-    public void init() throws Exception {
+    public AsymmetricKeys load() throws Exception {
         KeyStore keystore = KeyStore.getInstance(JKS);
         InputStream fis = 
fileSystem.getResource(jmapConfiguration.getKeystore());
         char[] secret = jmapConfiguration.getSecret().toCharArray();
@@ -74,41 +59,11 @@ public class JamesSignatureHandler implements 
SignatureHandler {
                 .ofNullable(keystore.getCertificate(ALIAS))
                 .orElseThrow(() -> new KeyStoreException("Alias '" + ALIAS + 
"' keystore can't be found"));
 
-        publicKey = aliasCertificate.getPublicKey();
+        PublicKey publicKey = aliasCertificate.getPublicKey();
         Key key = keystore.getKey(ALIAS, secret);
         if (! (key instanceof PrivateKey)) {
             throw new KeyStoreException("Provided key is not a PrivateKey");
         }
-        privateKey = (PrivateKey) key;
-    }
-
-    @Override
-    public String sign(String source) {
-        Preconditions.checkNotNull(source);
-        try {
-            Signature javaSignature = Signature.getInstance(ALGORITHM);
-            javaSignature.initSign(privateKey);
-            javaSignature.update(source.getBytes());
-            return Base64.getEncoder().encodeToString(javaSignature.sign());
-        } catch (NoSuchAlgorithmException | InvalidKeyException | 
SignatureException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    @Override
-    public boolean verify(String source, String signature) {
-        Preconditions.checkNotNull(source);
-        Preconditions.checkNotNull(signature);
-        try {
-            Signature javaSignature = Signature.getInstance(ALGORITHM);
-            javaSignature.initVerify(publicKey);
-            javaSignature.update(source.getBytes());
-            return javaSignature.verify(Base64.getDecoder().decode(signature));
-        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
-            throw new RuntimeException(e);
-        } catch (SignatureException e) {
-            LOGGER.warn("Attempt to use a malformed signature '{}' for source 
'{}'", signature, source, e);
-            return false;
-        }
+        return new AsymmetricKeys((PrivateKey) key, publicKey);
     }
 }
diff --git 
a/server/protocols/jmap/src/test/java/org/apache/james/jmap/crypto/ClassLoaderFileSystem.java
 
b/server/protocols/jmap/src/test/java/org/apache/james/jmap/crypto/ClassLoaderFileSystem.java
new file mode 100644
index 0000000..43687e4
--- /dev/null
+++ 
b/server/protocols/jmap/src/test/java/org/apache/james/jmap/crypto/ClassLoaderFileSystem.java
@@ -0,0 +1,45 @@
+/****************************************************************
+ * 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.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.james.filesystem.api.FileSystem;
+
+class ClassLoaderFileSystem implements FileSystem {
+
+    @Override
+    public InputStream getResource(String url) throws IOException {
+        return ClassLoader.getSystemResourceAsStream(url);
+    }
+
+    @Override
+    public File getFile(String fileURL) throws FileNotFoundException {
+        return null;
+    }
+
+    @Override
+    public File getBasedir() throws FileNotFoundException {
+        return null;
+    }
+}
diff --git 
a/server/protocols/jmap/src/test/java/org/apache/james/jmap/crypto/SecurityKeyLoaderTest.java
 
b/server/protocols/jmap/src/test/java/org/apache/james/jmap/crypto/SecurityKeyLoaderTest.java
new file mode 100644
index 0000000..63c1acb
--- /dev/null
+++ 
b/server/protocols/jmap/src/test/java/org/apache/james/jmap/crypto/SecurityKeyLoaderTest.java
@@ -0,0 +1,96 @@
+/****************************************************************
+ * 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.io.IOException;
+import java.security.KeyStoreException;
+import java.util.Optional;
+
+import org.apache.james.jmap.JMAPConfiguration;
+import org.junit.jupiter.api.Test;
+
+class SecurityKeyLoaderTest {
+
+    private static final String JWT_PUBLIC_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-----";
+
+    @Test
+    void loadShouldThrowWhenWrongKeystore() throws Exception {
+        JMAPConfiguration jmapConfiguration = JMAPConfiguration.builder()
+            .enable()
+            .jwtPublicKeyPem(Optional.of(JWT_PUBLIC_KEY))
+            .keystore("badAliasKeystore")
+            .secret("password")
+            .build();
+
+        SecurityKeyLoader loader = new SecurityKeyLoader(
+            new ClassLoaderFileSystem(),
+            jmapConfiguration);
+
+        assertThatThrownBy(loader::load)
+            .isInstanceOf(KeyStoreException.class)
+            .hasMessage("Alias 'james' keystore can't be found");
+    }
+
+    @Test
+    void loadShouldThrowWhenWrongPassword() throws Exception {
+        JMAPConfiguration jmapConfiguration = JMAPConfiguration.builder()
+            .enable()
+            .jwtPublicKeyPem(Optional.of(JWT_PUBLIC_KEY))
+            .keystore("keystore")
+            .secret("WrongPassword")
+            .build();
+
+        SecurityKeyLoader loader = new SecurityKeyLoader(
+            new ClassLoaderFileSystem(),
+            jmapConfiguration);
+
+        assertThatThrownBy(loader::load)
+            .isInstanceOf(IOException.class)
+            .hasMessage("Keystore was tampered with, or password was 
incorrect");
+    }
+
+    @Test
+    void loadShouldReturnSecurityKeysWhenCorrectPassword() throws Exception {
+        JMAPConfiguration jmapConfiguration = JMAPConfiguration.builder()
+            .enable()
+            .jwtPublicKeyPem(Optional.of(JWT_PUBLIC_KEY))
+            .keystore("keystore")
+            .secret("james72laBalle")
+            .build();
+
+        SecurityKeyLoader loader = new SecurityKeyLoader(
+            new ClassLoaderFileSystem(),
+            jmapConfiguration);
+
+        assertThat(loader.load())
+            .isNotNull();
+    }
+}
\ No newline at end of file


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to