You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ws.apache.org by co...@apache.org on 2020/06/08 08:50:39 UTC

[ws-wss4j] branch master updated: WSS-673 - Add caching for private keys in Merlin (#12)

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

coheigea pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ws-wss4j.git


The following commit(s) were added to refs/heads/master by this push:
     new 7707ffa  WSS-673 - Add caching for private keys in Merlin (#12)
7707ffa is described below

commit 7707ffa5bcb1d903eb8e21cbd059dc94649cd5a5
Author: Colm O hEigeartaigh <co...@users.noreply.github.com>
AuthorDate: Mon Jun 8 09:50:31 2020 +0100

    WSS-673 - Add caching for private keys in Merlin (#12)
---
 src/site/asciidoc/config.adoc                      |   1 +
 .../org/apache/wss4j/common/crypto/Merlin.java     |  36 +++++++++
 .../org/apache/wss4j/common/crypto/MerlinTest.java |  89 +++++++++++++++++++++
 .../src/test/resources/keys/wss40.p12              | Bin 0 -> 2557 bytes
 4 files changed, 126 insertions(+)

diff --git a/src/site/asciidoc/config.adoc b/src/site/asciidoc/config.adoc
index d550a5e..36a3b25 100644
--- a/src/site/asciidoc/config.adoc
+++ b/src/site/asciidoc/config.adoc
@@ -63,6 +63,7 @@ are as follows:
  * ${PREFIX}.merlin.keystore.type - Type of keystore. Defaults to: java.security.KeyStore.getDefaultType())
  * ${PREFIX}.merlin.keystore.alias - The default keystore alias to use, if none is specified.
  * ${PREFIX}.merlin.keystore.private.password - The default password used to load the private key.
+ * *WSS4J 2.3.0/2.2.6* ${PREFIX}.merlin.keystore.private.caching - Whether to enable caching when loading private keys or not. The default is true for WSS4J 2.3.0 and false for WSS4J 2.2.6. There is a significant performance gain for PKCS12 keys when caching is enabled.
 
 ==== Merlin TrustStore properties
 
diff --git a/ws-security-common/src/main/java/org/apache/wss4j/common/crypto/Merlin.java b/ws-security-common/src/main/java/org/apache/wss4j/common/crypto/Merlin.java
index 00ccc77..d5fdff7 100644
--- a/ws-security-common/src/main/java/org/apache/wss4j/common/crypto/Merlin.java
+++ b/ws-security-common/src/main/java/org/apache/wss4j/common/crypto/Merlin.java
@@ -55,8 +55,10 @@ import java.util.Collection;
 import java.util.Enumeration;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Properties;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.regex.Pattern;
 
 import javax.security.auth.callback.Callback;
@@ -101,6 +103,7 @@ public class Merlin extends CryptoBase {
     public static final String KEYSTORE_TYPE = "keystore.type";
     public static final String KEYSTORE_ALIAS = "keystore.alias";
     public static final String KEYSTORE_PRIVATE_PASSWORD = "keystore.private.password";
+    public static final String KEYSTORE_PRIVATE_KEY_CACHING = "keystore.private.caching";
 
     /*
      * TrustStore configuration types
@@ -129,6 +132,8 @@ public class Merlin extends CryptoBase {
     protected PasswordEncryptor passwordEncryptor;
 
     private boolean certProviderHandlesNameConstraints = false;
+    private boolean enablePrivateKeyCaching = true;
+    private Map<String, PrivateKey> privateKeyCache = new ConcurrentHashMap<>();
 
     public Merlin() {
         // default constructor
@@ -200,6 +205,7 @@ public class Merlin extends CryptoBase {
         if (cpNameConstraintsProp != null) {
             certProviderHandlesNameConstraints = Boolean.parseBoolean(cpNameConstraintsProp);
         }
+
         //
         // Load the KeyStore
         //
@@ -234,6 +240,11 @@ public class Merlin extends CryptoBase {
                     privatePasswordSet = true;
                 }
             }
+
+            String privateKeyCachingProp = properties.getProperty(prefix + KEYSTORE_PRIVATE_KEY_CACHING);
+            if (privateKeyCachingProp != null) {
+                enablePrivateKeyCaching = Boolean.parseBoolean(privateKeyCachingProp);
+            }
         } else {
             LOG.debug("The KeyStore is not loaded as KEYSTORE_FILE is null");
         }
@@ -704,6 +715,13 @@ public class Merlin extends CryptoBase {
                     pwd = decryptPassword(pwd, passwordEncryptor);
                 }
             }
+            if (enablePrivateKeyCaching) {
+                Key privateKey = privateKeyCache.get(identifier);
+                if (privateKey != null) {
+                    return (PrivateKey) privateKey;
+                }
+            }
+
             Key keyTmp = keystore.getKey(identifier, pwd == null
                                          ? new char[]{} : pwd.toCharArray());
             if (!(keyTmp instanceof PrivateKey)) {
@@ -713,6 +731,10 @@ public class Merlin extends CryptoBase {
                 throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "empty",
                                               new Object[] {msg});
             }
+
+            if (enablePrivateKeyCaching) {
+                privateKeyCache.put(identifier, (PrivateKey) keyTmp);
+            }
             return (PrivateKey) keyTmp;
         } catch (KeyStoreException | UnrecoverableKeyException | NoSuchAlgorithmException ex) {
             throw new WSSecurityException(
@@ -1519,4 +1541,18 @@ public class Merlin extends CryptoBase {
     public void setPasswordEncryptor(PasswordEncryptor passwordEncryptor) {
         this.passwordEncryptor = passwordEncryptor;
     }
+
+    public void clearCache() {
+        if (enablePrivateKeyCaching) {
+            privateKeyCache.clear();
+        }
+    }
+
+    public boolean isEnablePrivateKeyCaching() {
+        return enablePrivateKeyCaching;
+    }
+
+    public void setEnablePrivateKeyCaching(boolean enablePrivateKeyCaching) {
+        this.enablePrivateKeyCaching = enablePrivateKeyCaching;
+    }
 }
diff --git a/ws-security-common/src/test/java/org/apache/wss4j/common/crypto/MerlinTest.java b/ws-security-common/src/test/java/org/apache/wss4j/common/crypto/MerlinTest.java
new file mode 100644
index 0000000..a53443f
--- /dev/null
+++ b/ws-security-common/src/test/java/org/apache/wss4j/common/crypto/MerlinTest.java
@@ -0,0 +1,89 @@
+/**
+ * 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.wss4j.common.crypto;
+
+import java.io.InputStream;
+import java.security.KeyStore;
+
+import org.apache.wss4j.common.util.Loader;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.RepeatedTest;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+/**
+ * Some tests for the Merlin Crypto provider
+ */
+public class MerlinTest {
+
+    private static Merlin jksCrypto = new Merlin();
+    private static Merlin pkcs12Crypto = new Merlin();
+
+    @BeforeAll
+    public static void setup() throws Exception {
+        WSProviderConfig.init();
+        KeyStore keyStore = loadKeyStore("keys/wss40.jks", "security");
+        jksCrypto.setKeyStore(keyStore);
+
+        KeyStore pkcs12KeyStore = loadKeyStore("keys/wss40.p12", "security");
+        pkcs12Crypto.setKeyStore(pkcs12KeyStore);
+    }
+
+    @AfterAll
+    public static void cleanup() {
+        jksCrypto.clearCache();
+        pkcs12Crypto.clearCache();
+    }
+
+    @RepeatedTest(1000)
+    public void testGetPrivateKeyJKS() throws Exception {
+        assertNotNull(jksCrypto.getPrivateKey("wss40", "security"));
+    }
+
+    @RepeatedTest(1000)
+    public void testGetPrivateKeyPKCS12() throws Exception {
+        assertNotNull(pkcs12Crypto.getPrivateKey("wss40", "security"));
+    }
+
+    @RepeatedTest(1000)
+    public void testGetCertificateJKS() throws Exception {
+        CryptoType cryptoType = new CryptoType(CryptoType.TYPE.ALIAS);
+        cryptoType.setAlias("wss40");
+        assertNotNull(jksCrypto.getX509Certificates(cryptoType));
+    }
+
+    @RepeatedTest(1000)
+    public void testGetCertificatePKCS12() throws Exception {
+        CryptoType cryptoType = new CryptoType(CryptoType.TYPE.ALIAS);
+        cryptoType.setAlias("wss40");
+        assertNotNull(pkcs12Crypto.getX509Certificates(cryptoType));
+    }
+
+    private static KeyStore loadKeyStore(String path, String password) throws Exception {
+        KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
+        ClassLoader loader = Loader.getClassLoader(MerlinTest.class);
+        InputStream input = Merlin.loadInputStream(loader, path);
+        keyStore.load(input, password.toCharArray());
+        input.close();
+
+        return keyStore;
+    }
+}
\ No newline at end of file
diff --git a/ws-security-common/src/test/resources/keys/wss40.p12 b/ws-security-common/src/test/resources/keys/wss40.p12
new file mode 100644
index 0000000..40b3924
Binary files /dev/null and b/ws-security-common/src/test/resources/keys/wss40.p12 differ