You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by nt...@apache.org on 2022/11/16 14:50:49 UTC

[cayenne] 02/06: update CryptoModuleExtender

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

ntimofeev pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cayenne.git

commit 11ea4681a14c743ce9294aa4bb78575cf472e1f4
Author: Mikhail Dzianishchyts <mi...@gmail.com>
AuthorDate: Wed Nov 16 12:31:09 2022 +0300

    update CryptoModuleExtender
---
 .../org/apache/cayenne/crypto/CryptoModule.java    | 176 +++++---------
 .../cayenne/crypto/CryptoModuleExtender.java       | 253 +++++++--------------
 ...lderTest.java => CryptoModuleExtenderTest.java} |   7 +-
 .../apache/cayenne/crypto/Runtime_AES128_Base.java |  24 +-
 4 files changed, 158 insertions(+), 302 deletions(-)

diff --git a/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/CryptoModule.java b/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/CryptoModule.java
index 631b7b74e..d4e8de7a5 100644
--- a/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/CryptoModule.java
+++ b/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/CryptoModule.java
@@ -18,27 +18,13 @@
  */
 package org.apache.cayenne.crypto;
 
-import java.math.BigDecimal;
-import java.math.BigInteger;
-import java.sql.Types;
-import java.time.Duration;
-import java.time.LocalDate;
-import java.time.LocalDateTime;
-import java.time.LocalTime;
-import java.time.Period;
-import java.util.Date;
-
 import org.apache.cayenne.access.jdbc.reader.RowReaderFactory;
 import org.apache.cayenne.access.translator.batch.BatchTranslatorFactory;
 import org.apache.cayenne.configuration.DataMapLoader;
 import org.apache.cayenne.crypto.batch.CryptoBatchTranslatorFactoryDecorator;
-import org.apache.cayenne.crypto.cipher.CipherFactory;
 import org.apache.cayenne.crypto.cipher.DefaultCipherFactory;
 import org.apache.cayenne.crypto.key.JceksKeySource;
-import org.apache.cayenne.crypto.key.KeySource;
-import org.apache.cayenne.crypto.map.ColumnMapper;
 import org.apache.cayenne.crypto.map.CryptoDataMapLoader;
-import org.apache.cayenne.crypto.map.PatternColumnMapper;
 import org.apache.cayenne.crypto.reader.CryptoRowReaderFactoryDecorator;
 import org.apache.cayenne.crypto.transformer.DefaultTransformerFactory;
 import org.apache.cayenne.crypto.transformer.TransformerFactory;
@@ -50,7 +36,6 @@ import org.apache.cayenne.crypto.transformer.value.BigDecimalConverter;
 import org.apache.cayenne.crypto.transformer.value.BigIntegerConverter;
 import org.apache.cayenne.crypto.transformer.value.BooleanConverter;
 import org.apache.cayenne.crypto.transformer.value.ByteConverter;
-import org.apache.cayenne.crypto.transformer.value.BytesConverter;
 import org.apache.cayenne.crypto.transformer.value.BytesToBytesConverter;
 import org.apache.cayenne.crypto.transformer.value.DefaultValueTransformerFactory;
 import org.apache.cayenne.crypto.transformer.value.DoubleConverter;
@@ -68,9 +53,18 @@ import org.apache.cayenne.crypto.transformer.value.Utf8StringConverter;
 import org.apache.cayenne.crypto.transformer.value.UtilDateConverter;
 import org.apache.cayenne.crypto.transformer.value.ValueTransformerFactory;
 import org.apache.cayenne.di.Binder;
-import org.apache.cayenne.di.MapBuilder;
 import org.apache.cayenne.di.Module;
 
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.sql.Types;
+import java.time.Duration;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.Period;
+import java.util.Date;
+
 /**
  * Contains cryptography extensions for Cayenne.
  *
@@ -82,65 +76,75 @@ public class CryptoModule implements Module {
     private static final String DEFAULT_CIPHER_MODE = "CBC";
     private static final String DEFAULT_CIPHER_PADDING = "PKCS5Padding";
     // same as default keystore password in java...
+    // credentials are stored as char[] to potentially allow wiping them clean in memory...
     private static final char[] DEFAULT_KEY_PASSWORD = "changeit".toCharArray();
     private static final String DEFAULT_COLUMN_MAPPER_PATTERN = "^CRYPTO_";
 
     /**
-     * Returns a new extender that helps to build a custom module that provides required configuration for {@link CryptoModule}
+     * Returns a new extender that helps to build a custom module that provides required configuration for
+     * {@link CryptoModule}
      * as well as custom strategies overriding the defaults defined here.
      *
      * @return a new extender that helps to build a custom module that provides required configuration for {@link CryptoModule}.
+     * @since 5.0
      */
-    public static CryptoModuleExtender extend() {
-        return new CryptoModuleExtender();
-    }
-
-    static MapBuilder<String> contributeProperties(Binder binder) {
-        return binder.bindMap(String.class, CryptoConstants.PROPERTIES_MAP);
-    }
-
-    static MapBuilder<char[]> contributeCredentials(Binder binder) {
-        return binder.bindMap(char[].class, CryptoConstants.CREDENTIALS_MAP);
-    }
-
-    @SuppressWarnings("unchecked")
-    static MapBuilder<BytesConverter<?>> contributeDbToByteConverters(Binder binder) {
-        MapBuilder mapBuilder = binder.bindMap(BytesConverter.class, DefaultValueTransformerFactory.DB_TO_BYTE_CONVERTERS_KEY);
-        return (MapBuilder<BytesConverter<?>>) mapBuilder;
-    }
-
-    @SuppressWarnings("unchecked")
-    static MapBuilder<BytesConverter<?>> contributeObjectToByteConverters(Binder binder) {
-        MapBuilder mapBuilder = binder.bindMap(BytesConverter.class, DefaultValueTransformerFactory.OBJECT_TO_BYTE_CONVERTERS_KEY);
-        return (MapBuilder<BytesConverter<?>>) mapBuilder;
+    public static CryptoModuleExtender extend(Binder binder) {
+        return new CryptoModuleExtender(binder);
     }
 
     @Override
     public void configure(Binder binder) {
+        extend(binder)
+                .cipherAlgorithm(DEFAULT_CIPHER_ALGORITHM)
+                .cipherMode(DEFAULT_CIPHER_MODE)
+                .cipherPadding(DEFAULT_CIPHER_PADDING)
+                .cipherFactory(DefaultCipherFactory.class)
+
+                .keyStore((String) null, DEFAULT_KEY_PASSWORD, null)
+                .keySource(JceksKeySource.class)
+
+                .valueTransformerFactory(DefaultValueTransformerFactory.class)
+                .bytesTransformerFactory(DefaultBytesTransformerFactory.class)
+
+                .columnMapper(DEFAULT_COLUMN_MAPPER_PATTERN)
+
+                .objectToBytesConverter("byte[]", BytesToBytesConverter.INSTANCE)
+                .objectToBytesConverter(String.class, Utf8StringConverter.INSTANCE)
+
+                .objectToBytesConverter(Double.class, DoubleConverter.INSTANCE)
+                .objectToBytesConverter(Float.class, FloatConverter.INSTANCE)
+                .objectToBytesConverter(Long.class, LongConverter.INSTANCE)
+                .objectToBytesConverter(Integer.class, IntegerConverter.INSTANCE)
+                .objectToBytesConverter(Short.class, ShortConverter.INSTANCE)
+                .objectToBytesConverter(Byte.class, ByteConverter.INSTANCE)
+                .objectToBytesConverter(Boolean.class, BooleanConverter.INSTANCE)
+
+                .objectToBytesConverter(BigInteger.class, BigIntegerConverter.INSTANCE)
+                .objectToBytesConverter(BigDecimal.class, BigDecimalConverter.INSTANCE)
+                .objectToBytesConverter(Date.class, UtilDateConverter.INSTANCE)
+                .objectToBytesConverter(LocalDate.class, LocalDateConverter.INSTANCE)
+                .objectToBytesConverter(LocalTime.class, LocalTimeConverter.INSTANCE)
+                .objectToBytesConverter(LocalDateTime.class, LocalDateTimeConverter.INSTANCE)
+                .objectToBytesConverter(Duration.class, DurationConverter.INSTANCE)
+                .objectToBytesConverter(Period.class, PeriodConverter.INSTANCE)
+
+                .dbToBytesConverter(Types.BINARY, BytesToBytesConverter.INSTANCE)
+                .dbToBytesConverter(Types.BLOB, BytesToBytesConverter.INSTANCE)
+                .dbToBytesConverter(Types.VARBINARY, BytesToBytesConverter.INSTANCE)
+                .dbToBytesConverter(Types.LONGVARBINARY, BytesToBytesConverter.INSTANCE)
+
+                .dbToBytesConverter(Types.CHAR, Base64StringConverter.INSTANCE)
+                .dbToBytesConverter(Types.NCHAR, Base64StringConverter.INSTANCE)
+                .dbToBytesConverter(Types.CLOB, Base64StringConverter.INSTANCE)
+                .dbToBytesConverter(Types.NCLOB, Base64StringConverter.INSTANCE)
+                .dbToBytesConverter(Types.LONGVARCHAR, Base64StringConverter.INSTANCE)
+                .dbToBytesConverter(Types.LONGNVARCHAR, Base64StringConverter.INSTANCE)
+                .dbToBytesConverter(Types.VARCHAR, Base64StringConverter.INSTANCE)
+                .dbToBytesConverter(Types.NVARCHAR, Base64StringConverter.INSTANCE);
 
-        contributeProperties(binder)
-                .put(CryptoConstants.CIPHER_ALGORITHM, DEFAULT_CIPHER_ALGORITHM)
-                .put(CryptoConstants.CIPHER_MODE, DEFAULT_CIPHER_MODE)
-                .put(CryptoConstants.CIPHER_PADDING, DEFAULT_CIPHER_PADDING);
-
-        // credentials are stored as char[] to potentially allow wiping them clean in memory...
-        contributeCredentials(binder).put(CryptoConstants.KEY_PASSWORD, DEFAULT_KEY_PASSWORD);
-
-        binder.bind(CipherFactory.class).to(DefaultCipherFactory.class);
         binder.bind(TransformerFactory.class).to(DefaultTransformerFactory.class);
-        binder.bind(ValueTransformerFactory.class).to(DefaultValueTransformerFactory.class);
-
-        MapBuilder<BytesConverter<?>> dbToBytesBinder = contributeDbToByteConverters(binder);
-        contributeDefaultDbConverters(dbToBytesBinder);
-
-        MapBuilder<BytesConverter<?>> objectToBytesBinder = contributeObjectToByteConverters(binder);
-        contributeDefaultObjectConverters(objectToBytesBinder);
 
-        binder.bind(BytesTransformerFactory.class).to(DefaultBytesTransformerFactory.class);
-        binder.bind(KeySource.class).to(JceksKeySource.class);
-        binder.bind(ColumnMapper.class).toInstance(new PatternColumnMapper(DEFAULT_COLUMN_MAPPER_PATTERN));
         binder.decorate(DataMapLoader.class).before(CryptoDataMapLoader.class);
-        
         binder.decorate(BatchTranslatorFactory.class).before(CryptoBatchTranslatorFactoryDecorator.class);
         binder.bind(RowReaderFactory.class).to(CryptoRowReaderFactoryDecorator.class);
 
@@ -149,58 +153,4 @@ public class CryptoModule implements Module {
         binder.decorate(ValueTransformerFactory.class).after(LazyValueTransformerFactory.class);
         binder.decorate(BytesTransformerFactory.class).after(LazyBytesTransformerFactory.class);
     }
-
-    private static void contributeDefaultDbConverters(MapBuilder<BytesConverter<?>> mapBuilder) {
-
-        mapBuilder.put(String.valueOf(Types.BINARY), BytesToBytesConverter.INSTANCE);
-        mapBuilder.put(String.valueOf(Types.BLOB), BytesToBytesConverter.INSTANCE);
-        mapBuilder.put(String.valueOf(Types.VARBINARY), BytesToBytesConverter.INSTANCE);
-        mapBuilder.put(String.valueOf(Types.LONGVARBINARY), BytesToBytesConverter.INSTANCE);
-
-        mapBuilder.put(String.valueOf(Types.CHAR), Base64StringConverter.INSTANCE);
-        mapBuilder.put(String.valueOf(Types.NCHAR), Base64StringConverter.INSTANCE);
-        mapBuilder.put(String.valueOf(Types.CLOB), Base64StringConverter.INSTANCE);
-        mapBuilder.put(String.valueOf(Types.NCLOB), Base64StringConverter.INSTANCE);
-        mapBuilder.put(String.valueOf(Types.LONGVARCHAR), Base64StringConverter.INSTANCE);
-        mapBuilder.put(String.valueOf(Types.LONGNVARCHAR), Base64StringConverter.INSTANCE);
-        mapBuilder.put(String.valueOf(Types.VARCHAR), Base64StringConverter.INSTANCE);
-        mapBuilder.put(String.valueOf(Types.NVARCHAR), Base64StringConverter.INSTANCE);
-    }
-
-    private static void contributeDefaultObjectConverters(MapBuilder<BytesConverter<?>> mapBuilder) {
-
-        mapBuilder.put("byte[]", BytesToBytesConverter.INSTANCE);
-        mapBuilder.put(String.class.getName(), Utf8StringConverter.INSTANCE);
-
-        mapBuilder.put(Double.class.getName(), DoubleConverter.INSTANCE);
-        mapBuilder.put(Double.TYPE.getName(), DoubleConverter.INSTANCE);
-
-        mapBuilder.put(Float.class.getName(), FloatConverter.INSTANCE);
-        mapBuilder.put(Float.TYPE.getName(), FloatConverter.INSTANCE);
-
-        mapBuilder.put(Long.class.getName(), LongConverter.INSTANCE);
-        mapBuilder.put(Long.TYPE.getName(), LongConverter.INSTANCE);
-
-        mapBuilder.put(Integer.class.getName(), IntegerConverter.INSTANCE);
-        mapBuilder.put(Integer.TYPE.getName(), IntegerConverter.INSTANCE);
-
-        mapBuilder.put(Short.class.getName(), ShortConverter.INSTANCE);
-        mapBuilder.put(Short.TYPE.getName(), ShortConverter.INSTANCE);
-
-        mapBuilder.put(Byte.class.getName(), ByteConverter.INSTANCE);
-        mapBuilder.put(Byte.TYPE.getName(), ByteConverter.INSTANCE);
-
-        mapBuilder.put(Boolean.class.getName(), BooleanConverter.INSTANCE);
-        mapBuilder.put(Boolean.TYPE.getName(), BooleanConverter.INSTANCE);
-
-        mapBuilder.put(Date.class.getName(), UtilDateConverter.INSTANCE);
-        mapBuilder.put(BigInteger.class.getName(), BigIntegerConverter.INSTANCE);
-        mapBuilder.put(BigDecimal.class.getName(), BigDecimalConverter.INSTANCE);
-
-        mapBuilder.put(LocalDate.class.getName(), LocalDateConverter.INSTANCE);
-        mapBuilder.put(LocalTime.class.getName(), LocalTimeConverter.INSTANCE);
-        mapBuilder.put(LocalDateTime.class.getName(), LocalDateTimeConverter.INSTANCE);
-        mapBuilder.put(Duration.class.getName(), DurationConverter.INSTANCE);
-        mapBuilder.put(Period.class.getName(), PeriodConverter.INSTANCE);
-    }
 }
diff --git a/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/CryptoModuleExtender.java b/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/CryptoModuleExtender.java
index b3720e680..0adfb9213 100644
--- a/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/CryptoModuleExtender.java
+++ b/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/CryptoModuleExtender.java
@@ -24,15 +24,14 @@ import org.apache.cayenne.crypto.map.ColumnMapper;
 import org.apache.cayenne.crypto.map.PatternColumnMapper;
 import org.apache.cayenne.crypto.transformer.bytes.BytesTransformerFactory;
 import org.apache.cayenne.crypto.transformer.value.BytesConverter;
+import org.apache.cayenne.crypto.transformer.value.DefaultValueTransformerFactory;
 import org.apache.cayenne.crypto.transformer.value.ValueTransformerFactory;
+import org.apache.cayenne.di.Binder;
 import org.apache.cayenne.di.MapBuilder;
-import org.apache.cayenne.di.Module;
 
 import java.io.File;
 import java.net.MalformedURLException;
 import java.net.URL;
-import java.util.HashMap;
-import java.util.Map;
 import java.util.Objects;
 
 /**
@@ -42,91 +41,75 @@ import java.util.Objects;
  */
 public class CryptoModuleExtender {
 
-    private Class<? extends ValueTransformerFactory> valueTransformerFactoryType;
-    private Class<? extends BytesTransformerFactory> bytesTransformerFactoryType;
+    private final Binder binder;
 
-    private Map<String, BytesConverter<?>> extraObjectToBytes;
-    private Map<Integer, BytesConverter<?>> extraDbToBytes;
+    private MapBuilder<String> properties;
+    private MapBuilder<char[]> credentials;
+    private MapBuilder<BytesConverter<?>> dbToByteConverters;
+    private MapBuilder<BytesConverter<?>> objectToByteConverters;
 
-    private String columnMapperPattern;
-    private ColumnMapper columnMapper;
-    private Class<? extends ColumnMapper> columnMapperType;
-
-    private String cipherAlgorithm;
-    private String cipherMode;
-    private Class<? extends CipherFactory> cipherFactoryType;
-
-    private URL keyStoreUrl;
-    private String keyStoreUrlString;
-    private File keyStoreFile;
-    private Class<? extends KeySource> keySourceType;
-    private KeySource keySource;
-
-    private String encryptionKeyAlias;
-    private char[] keyPassword;
-
-    private boolean compress;
-    private boolean useHMAC;
-
-    // use CryptoModule.builder() to create the builder...
-    protected CryptoModuleExtender() {
-        this.extraDbToBytes = new HashMap<>();
-        this.extraObjectToBytes = new HashMap<>();
+    protected CryptoModuleExtender(Binder binder) {
+        this.binder = binder;
     }
 
     public CryptoModuleExtender cipherAlgorithm(String algorithm) {
-        this.cipherAlgorithm = Objects.requireNonNull(algorithm);
+        contributeProperties(binder).put(CryptoConstants.CIPHER_ALGORITHM, algorithm);
         return this;
     }
 
     public CryptoModuleExtender cipherMode(String mode) {
-        this.cipherMode = Objects.requireNonNull(mode);
+        contributeProperties(binder).put(CryptoConstants.CIPHER_MODE, mode);
+        return this;
+    }
+
+    public CryptoModuleExtender cipherPadding(String padding) {
+        contributeProperties(binder).put(CryptoConstants.CIPHER_PADDING, padding);
         return this;
     }
 
     public CryptoModuleExtender cipherFactory(Class<? extends CipherFactory> factoryType) {
-        this.cipherFactoryType = Objects.requireNonNull(factoryType);
+        binder.bind(CipherFactory.class).to(factoryType);
+        return this;
+    }
+
+    public CryptoModuleExtender valueTransformerFactory(Class<? extends ValueTransformerFactory> factoryType) {
+        binder.bind(ValueTransformerFactory.class).to(factoryType);
         return this;
     }
 
-    public CryptoModuleExtender valueTransformer(Class<? extends ValueTransformerFactory> factoryType) {
-        this.valueTransformerFactoryType = Objects.requireNonNull(factoryType);
+    public CryptoModuleExtender bytesTransformerFactory(Class<? extends BytesTransformerFactory> factoryType) {
+        binder.bind(BytesTransformerFactory.class).to(factoryType);
         return this;
     }
 
     public <T> CryptoModuleExtender objectToBytesConverter(Class<T> objectType, BytesConverter<T> converter) {
-        extraObjectToBytes.put(objectType.getName(), Objects.requireNonNull(converter));
+        contributeObjectToByteConverters(binder).put(objectType.getName(), Objects.requireNonNull(converter));
         return this;
     }
 
-    public CryptoModuleExtender dbToBytesConverter(int sqlType, BytesConverter<?> converter) {
-        extraDbToBytes.put(sqlType, Objects.requireNonNull(converter));
+    // workaround for byte[]
+    CryptoModuleExtender objectToBytesConverter(String objectTypeName, BytesConverter<Object> converter) {
+        contributeObjectToByteConverters(binder).put(objectTypeName, Objects.requireNonNull(converter));
         return this;
     }
 
-    public CryptoModuleExtender bytesTransformer(Class<? extends BytesTransformerFactory> factoryType) {
-        this.bytesTransformerFactoryType = Objects.requireNonNull(factoryType);
+    public CryptoModuleExtender dbToBytesConverter(int sqlType, BytesConverter<?> converter) {
+        contributeDbToByteConverters(binder).put(String.valueOf(sqlType), Objects.requireNonNull(converter));
         return this;
     }
 
     public CryptoModuleExtender columnMapper(Class<? extends ColumnMapper> columnMapperType) {
-        this.columnMapperPattern = null;
-        this.columnMapperType = Objects.requireNonNull(columnMapperType);
-        this.columnMapper = null;
+        binder.bind(ColumnMapper.class).to(columnMapperType);
         return this;
     }
 
     public CryptoModuleExtender columnMapper(ColumnMapper columnMapper) {
-        this.columnMapperPattern = null;
-        this.columnMapperType = null;
-        this.columnMapper = Objects.requireNonNull(columnMapper);
+        binder.bind(ColumnMapper.class).toInstance(columnMapper);
         return this;
     }
 
     public CryptoModuleExtender columnMapper(String pattern) {
-        this.columnMapperPattern = Objects.requireNonNull(pattern);
-        this.columnMapperType = null;
-        this.columnMapper = null;
+        binder.bind(ColumnMapper.class).toInstance(new PatternColumnMapper(pattern));
         return this;
     }
 
@@ -135,13 +118,13 @@ public class CryptoModuleExtender {
      *                           encryption by default.
      */
     public CryptoModuleExtender encryptionKeyAlias(String encryptionKeyAlias) {
-        this.encryptionKeyAlias = Objects.requireNonNull(encryptionKeyAlias);
+        contributeProperties(binder).put(CryptoConstants.ENCRYPTION_KEY_ALIAS, encryptionKeyAlias);
         return this;
     }
 
     /**
      * Configures keystore parameters. The KeyStore must be of "jceks" type and
-     * contain all needed secret keys for the target database. Currently all
+     * contain all needed secret keys for the target database. Currently, all
      * keys must be protected with the same password.
      *
      * @param file               A file to load keystore from.
@@ -150,17 +133,17 @@ public class CryptoModuleExtender {
      *                           encryption by default.
      */
     public CryptoModuleExtender keyStore(File file, char[] passwordForAllKeys, String encryptionKeyAlias) {
-        this.encryptionKeyAlias = encryptionKeyAlias;
-        this.keyPassword = passwordForAllKeys;
-        this.keyStoreUrl = null;
-        this.keyStoreUrlString = null;
-        this.keyStoreFile = Objects.requireNonNull(file);
-        return this;
+        try {
+            String fileUrl = file.toURI().toURL().toExternalForm();
+            return keyStore(fileUrl, passwordForAllKeys, encryptionKeyAlias);
+        } catch (MalformedURLException e) {
+            throw new IllegalArgumentException("Invalid keyStore file", e);
+        }
     }
 
     /**
      * Configures keystore parameters. The KeyStore must be of "jceks" type and
-     * contain all needed secret keys for the target database. Currently all
+     * contain all needed secret keys for the target database. Currently, all
      * keys must be protected with the same password.
      *
      * @param url                A URL to load keystore from.
@@ -168,18 +151,13 @@ public class CryptoModuleExtender {
      * @param encryptionKeyAlias The name of the key in the keystore that should be used for
      *                           encryption by default.
      */
-    public CryptoModuleExtender keyStore(String url, char[] passwordForAllKeys, String encryptionKeyAlias) {
-        this.encryptionKeyAlias = encryptionKeyAlias;
-        this.keyPassword = passwordForAllKeys;
-        this.keyStoreUrl = null;
-        this.keyStoreUrlString = Objects.requireNonNull(url);
-        this.keyStoreFile = null;
-        return this;
+    public CryptoModuleExtender keyStore(URL url, char[] passwordForAllKeys, String encryptionKeyAlias) {
+        return keyStore(url.toExternalForm(), passwordForAllKeys, encryptionKeyAlias);
     }
 
     /**
      * Configures keystore parameters. The KeyStore must be of "jceks" type and
-     * contain all needed secret keys for the target database. Currently all
+     * contain all needed secret keys for the target database. Currently, all
      * keys must be protected with the same password.
      *
      * @param url                A URL to load keystore from.
@@ -187,29 +165,26 @@ public class CryptoModuleExtender {
      * @param encryptionKeyAlias The name of the key in the keystore that should be used for
      *                           encryption by default.
      */
-    public CryptoModuleExtender keyStore(URL url, char[] passwordForAllKeys, String encryptionKeyAlias) {
-        this.encryptionKeyAlias = encryptionKeyAlias;
-        this.keyPassword = passwordForAllKeys;
-        this.keyStoreUrl = Objects.requireNonNull(url);
-        this.keyStoreUrlString = null;
-        this.keyStoreFile = null;
+    public CryptoModuleExtender keyStore(String url, char[] passwordForAllKeys, String encryptionKeyAlias) {
+        MapBuilder<String> propertiesBuilder = contributeProperties(binder);
+        propertiesBuilder.put(CryptoConstants.KEYSTORE_URL, url);
+        propertiesBuilder.put(CryptoConstants.ENCRYPTION_KEY_ALIAS, encryptionKeyAlias);
+        contributeCredentials(binder).put(CryptoConstants.KEY_PASSWORD, passwordForAllKeys);
         return this;
     }
 
-    public CryptoModuleExtender keySource(Class<? extends KeySource> type) {
-        this.keySourceType = Objects.requireNonNull(type);
-        this.keySource = null;
+    public CryptoModuleExtender keySource(Class<? extends KeySource> keySourceType) {
+        binder.bind(KeySource.class).to(keySourceType);
         return this;
     }
 
     public CryptoModuleExtender keySource(KeySource keySource) {
-        this.keySourceType = null;
-        this.keySource = Objects.requireNonNull(keySource);
+        binder.bind(KeySource.class).toInstance(keySource);
         return this;
     }
 
     public CryptoModuleExtender compress() {
-        this.compress = true;
+        contributeProperties(binder).put(CryptoConstants.COMPRESSION, "true");
         return this;
     }
 
@@ -217,109 +192,41 @@ public class CryptoModuleExtender {
      * Enable authentication codes
      */
     public CryptoModuleExtender useHMAC() {
-        this.useHMAC = true;
+        contributeProperties(binder).put(CryptoConstants.USE_HMAC, "true");
         return this;
     }
 
-    /**
-     * Produces a module that can be used to start Cayenne runtime.
-     */
-    public Module module() {
-
-        return binder -> {
-
-            MapBuilder<String> props = CryptoModule.contributeProperties(binder);
-
-            if (cipherAlgorithm != null) {
-                props.put(CryptoConstants.CIPHER_ALGORITHM, cipherAlgorithm);
-            }
-
-            if (cipherMode != null) {
-                props.put(CryptoConstants.CIPHER_MODE, cipherMode);
-            }
-
-            String keyStoreUrl = keyStoreUrl();
-            if (keyStoreUrl != null) {
-                props.put(CryptoConstants.KEYSTORE_URL, keyStoreUrl);
-            }
-
-            if (encryptionKeyAlias != null) {
-                props.put(CryptoConstants.ENCRYPTION_KEY_ALIAS, encryptionKeyAlias);
-            }
-
-            if (compress) {
-                props.put(CryptoConstants.COMPRESSION, "true");
-            }
-
-            if (useHMAC) {
-                props.put(CryptoConstants.USE_HMAC, "true");
-            }
-
-            if (keyPassword != null) {
-                CryptoModule.contributeCredentials(binder).put(CryptoConstants.KEY_PASSWORD, keyPassword);
-            }
-
-            if (cipherFactoryType != null) {
-                binder.bind(CipherFactory.class).to(cipherFactoryType);
-            }
-
-            if (valueTransformerFactoryType != null) {
-                binder.bind(ValueTransformerFactory.class).to(valueTransformerFactoryType);
-            }
-
-            if (!extraDbToBytes.isEmpty()) {
-                MapBuilder<BytesConverter<?>> dbToBytesBinder = CryptoModule.contributeDbToByteConverters(binder);
-                for (Map.Entry<Integer, BytesConverter<?>> extraConverter : extraDbToBytes.entrySet()) {
-                    dbToBytesBinder.put(extraConverter.getKey().toString(), extraConverter.getValue());
-                }
-            }
-
-            if (!extraObjectToBytes.isEmpty()) {
-                MapBuilder<BytesConverter<?>> objectToBytesBinder = CryptoModule.contributeObjectToByteConverters(binder);
-                for (Map.Entry<String, BytesConverter<?>> extraConverter : extraObjectToBytes.entrySet()) {
-                    objectToBytesBinder.put(extraConverter.getKey(), extraConverter.getValue());
-                }
-            }
-
-            if (bytesTransformerFactoryType != null) {
-                binder.bind(BytesTransformerFactory.class).to(bytesTransformerFactoryType);
-            }
-
-            if (keySource != null) {
-                binder.bind(KeySource.class).toInstance(keySource);
-            } else if (keySourceType != null) {
-                binder.bind(KeySource.class).to(keySourceType);
-            }
-
-            if (columnMapperPattern != null) {
-                binder.bind(ColumnMapper.class).toInstance(new PatternColumnMapper(columnMapperPattern));
-            } else if (columnMapperType != null) {
-                binder.bind(ColumnMapper.class).to(columnMapperType);
-            } else if (columnMapper != null) {
-                binder.bind(ColumnMapper.class).toInstance(columnMapper);
-            }
-        };
+    private MapBuilder<String> contributeProperties(Binder binder) {
+        if (properties == null) {
+            properties = binder.bindMap(String.class, CryptoConstants.PROPERTIES_MAP);
+        }
+        return properties;
     }
 
-    protected String keyStoreUrl() {
-        if (this.keyStoreUrl != null) {
-            return this.keyStoreUrl.toExternalForm();
+    private MapBuilder<char[]> contributeCredentials(Binder binder) {
+        if (credentials == null) {
+            credentials = binder.bindMap(char[].class, CryptoConstants.CREDENTIALS_MAP);
         }
+        return credentials;
+    }
 
-        if (this.keyStoreUrlString != null) {
-            return this.keyStoreUrlString;
+    @SuppressWarnings({"rawtypes", "unchecked"})
+    private MapBuilder<BytesConverter<?>> contributeDbToByteConverters(Binder binder) {
+        if (dbToByteConverters == null) {
+            MapBuilder mapBuilder = binder.bindMap(BytesConverter.class,
+                                                   DefaultValueTransformerFactory.DB_TO_BYTE_CONVERTERS_KEY);
+            dbToByteConverters = mapBuilder;
         }
+        return dbToByteConverters;
+    }
 
-        if (keyStoreFile != null) {
-            try {
-                return keyStoreFile.toURI().toURL().toExternalForm();
-            } catch (MalformedURLException e) {
-                throw new IllegalStateException("Invalid keyStore file", e);
-            }
+    @SuppressWarnings({"rawtypes", "unchecked"})
+    private MapBuilder<BytesConverter<?>> contributeObjectToByteConverters(Binder binder) {
+        if (objectToByteConverters == null) {
+            MapBuilder mapBuilder = binder.bindMap(BytesConverter.class,
+                                                   DefaultValueTransformerFactory.OBJECT_TO_BYTE_CONVERTERS_KEY);
+            objectToByteConverters = mapBuilder;
         }
-
-        return null;
+        return objectToByteConverters;
     }
-
-
 }
diff --git a/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/CryptoModuleBuilderTest.java b/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/CryptoModuleExtenderTest.java
similarity index 89%
rename from cayenne-crypto/src/test/java/org/apache/cayenne/crypto/CryptoModuleBuilderTest.java
rename to cayenne-crypto/src/test/java/org/apache/cayenne/crypto/CryptoModuleExtenderTest.java
index 4e79817d9..20e03a900 100644
--- a/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/CryptoModuleBuilderTest.java
+++ b/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/CryptoModuleExtenderTest.java
@@ -32,14 +32,13 @@ import java.security.Key;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 
-public class CryptoModuleBuilderTest {
+public class CryptoModuleExtenderTest {
 
     @Test
     public void testBuild_KeySource() {
-
         URL ksUrl = JceksKeySourceTest.class.getResource(JceksKeySourceTest.KS1_JCEKS);
-        Module m = new CryptoModuleExtender().keyStore(ksUrl, JceksKeySourceTest.TEST_KEY_PASS, "k1")
-                .valueTransformer(DefaultValueTransformerFactory.class).module();
+        Module m = b -> new CryptoModuleExtender(b).keyStore(ksUrl, JceksKeySourceTest.TEST_KEY_PASS, "k1")
+                .valueTransformerFactory(DefaultValueTransformerFactory.class);
 
         Injector injector = DIBootstrap.createInjector(new CryptoModule(), m);
 
diff --git a/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/Runtime_AES128_Base.java b/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/Runtime_AES128_Base.java
index 8eb9b16d8..43c6873a3 100644
--- a/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/Runtime_AES128_Base.java
+++ b/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/Runtime_AES128_Base.java
@@ -66,18 +66,18 @@ public class Runtime_AES128_Base {
     protected Module createCryptoModule(boolean compress, boolean useHMAC) {
         URL keyStoreUrl = JceksKeySourceTest.class.getResource(JceksKeySourceTest.KS1_JCEKS);
 
-        CryptoModuleExtender builder = CryptoModule
-                .extend()
-                .keyStore(keyStoreUrl, JceksKeySourceTest.TEST_KEY_PASS, "k3");
-
-        if (compress) {
-            builder.compress();
-        }
-        if(useHMAC) {
-            builder.useHMAC();
-        }
-
-        return builder.module();
+        Module module = b -> {
+            CryptoModuleExtender moduleExtender = CryptoModule.extend(b)
+                    .keyStore(keyStoreUrl, JceksKeySourceTest.TEST_KEY_PASS, "k3");
+            if (compress) {
+                moduleExtender.compress();
+            }
+            if(useHMAC) {
+                moduleExtender.useHMAC();
+            }
+        };
+
+        return module;
     }
 
 }