Skip to content

Commit 7cace3b

Browse files
authored
Crypto common module integrated into config and vault (helidon-io#3127)
* Crypto common module integrated into config and vault Signed-off-by: David Kral <david.k.kral@oracle.com>
1 parent e04e3ae commit 7cace3b

15 files changed

Lines changed: 116 additions & 115 deletions

File tree

bom/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,11 @@
603603
<artifactId>helidon-service-common-rest-cdi</artifactId>
604604
<version>${helidon.version}</version>
605605
</dependency>
606+
<dependency>
607+
<groupId>io.helidon.common</groupId>
608+
<artifactId>helidon-common-crypto</artifactId>
609+
<version>${helidon.version}</version>
610+
</dependency>
606611

607612
<!-- db client -->
608613
<dependency>

common/crypto/src/main/java/io/helidon/common/crypto/AsymmetricCipher.java

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -106,31 +106,64 @@ public static Builder builder() {
106106
return new Builder();
107107
}
108108

109+
/**
110+
* Encrypt the message with the provided public key and selected algorithm.
111+
*
112+
* @param algorithm algorithm name
113+
* @param provider algorithm provider
114+
* @param publicKey public key used for encryption
115+
* @param message message to be encrypted
116+
* @return encrypted message
117+
*/
118+
public static Base64Value encrypt(String algorithm, String provider, PublicKey publicKey, Base64Value message) {
119+
Objects.requireNonNull(algorithm, "Algorithm for encryption cannot be null");
120+
Objects.requireNonNull(publicKey, "Public key cannot be null");
121+
Objects.requireNonNull(message, "Message cannot be null");
122+
try {
123+
return performCryptoOperation(Cipher.ENCRYPT_MODE, algorithm, provider, publicKey, message);
124+
} catch (Exception e) {
125+
throw new CryptoException("Message could not be encrypted", e);
126+
}
127+
}
128+
129+
/**
130+
* Decrypt the message with the provided private key and selected algorithm.
131+
*
132+
* @param algorithm algorithm name
133+
* @param provider algorithm provider
134+
* @param privateKey private key used for decryption
135+
* @param message message to be decrypted
136+
* @return decrypted message
137+
*/
138+
public static Base64Value decrypt(String algorithm, String provider, PrivateKey privateKey, Base64Value message) {
139+
Objects.requireNonNull(algorithm, "Algorithm for decryption cannot be null");
140+
Objects.requireNonNull(privateKey, "Private key cannot be null");
141+
Objects.requireNonNull(message, "Message cannot be null");
142+
try {
143+
return performCryptoOperation(Cipher.DECRYPT_MODE, algorithm, provider, privateKey, message);
144+
} catch (Exception e) {
145+
throw new CryptoException("Message could not be decrypted", e);
146+
}
147+
}
148+
109149
@Override
110150
public Base64Value encrypt(Base64Value message) {
111151
if (publicKey == null) {
112152
throw new CryptoException("No public key present. Could not perform encrypt operation");
113153
}
114-
try {
115-
return performCryptoOperation(Cipher.ENCRYPT_MODE, publicKey, message);
116-
} catch (Exception e) {
117-
throw new CryptoException("Message could not be encrypted", e);
118-
}
154+
return encrypt(algorithm, provider, publicKey, message);
119155
}
120156

121157
@Override
122158
public Base64Value decrypt(Base64Value encrypted) {
123159
if (privateKey == null) {
124160
throw new CryptoException("No private key present. Could not perform decryption operation");
125161
}
126-
try {
127-
return performCryptoOperation(Cipher.DECRYPT_MODE, privateKey, encrypted);
128-
} catch (Exception e) {
129-
throw new CryptoException("Message could not be decrypted", e);
130-
}
162+
return decrypt(algorithm, provider, privateKey, encrypted);
131163
}
132164

133-
private Base64Value performCryptoOperation(int mode, Key key, Base64Value data) throws Exception {
165+
private static Base64Value performCryptoOperation(int mode, String algorithm, String provider, Key key, Base64Value data)
166+
throws Exception {
134167
Cipher cipher;
135168
if (provider == null) {
136169
cipher = Cipher.getInstance(algorithm);

config/encryption/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@
4747
<groupId>io.helidon.common</groupId>
4848
<artifactId>helidon-common-key-util</artifactId>
4949
</dependency>
50+
<dependency>
51+
<groupId>io.helidon.common</groupId>
52+
<artifactId>helidon-common-crypto</artifactId>
53+
</dependency>
5054
<dependency>
5155
<!-- an implementation of a filter is part of this library - it only makes sense
5256
when MP config implementation is on the classpath, not needed or used otherwise

config/encryption/src/main/java/io/helidon/config/encryption/EncryptionUtil.java

Lines changed: 18 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,16 @@
3030

3131
import javax.crypto.Cipher;
3232
import javax.crypto.SecretKeyFactory;
33-
import javax.crypto.spec.GCMParameterSpec;
3433
import javax.crypto.spec.IvParameterSpec;
3534
import javax.crypto.spec.PBEKeySpec;
3635
import javax.crypto.spec.SecretKeySpec;
3736

37+
import io.helidon.common.Base64Value;
3838
import io.helidon.common.LazyValue;
3939
import io.helidon.common.configurable.Resource;
40+
import io.helidon.common.crypto.AsymmetricCipher;
41+
import io.helidon.common.crypto.PasswordKeyDerivation;
42+
import io.helidon.common.crypto.SymmetricCipher;
4043
import io.helidon.common.pki.KeyConfig;
4144
import io.helidon.config.Config;
4245
import io.helidon.config.ConfigValue;
@@ -57,7 +60,6 @@ public final class EncryptionUtil {
5760
private static final int HASH_ITERATIONS = 10000;
5861
private static final int KEY_LENGTH_LEGACY = 128;
5962
private static final int KEY_LENGTH = 256;
60-
private static final int AUTHENTICATION_TAG_LENGTH = 128;
6163

6264
private EncryptionUtil() {
6365
throw new IllegalStateException("Utility class");
@@ -77,10 +79,8 @@ public static String decryptRsa(PrivateKey key, String encryptedBase64) throws C
7779
Objects.requireNonNull(encryptedBase64, "Encrypted bytes must be provided for decryption (base64 encoded)");
7880

7981
try {
80-
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
81-
cipher.init(Cipher.DECRYPT_MODE, key);
82-
byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(encryptedBase64));
83-
return new String(decrypted, StandardCharsets.UTF_8);
82+
Base64Value value = Base64Value.createFromEncoded(encryptedBase64);
83+
return AsymmetricCipher.decrypt(AsymmetricCipher.ALGORITHM_RSA_ECB_OAEP256, null, key, value).toDecodedString();
8484
} catch (ConfigEncryptionException e) {
8585
throw e;
8686
} catch (Exception e) {
@@ -129,12 +129,9 @@ public static String encryptRsa(PublicKey key, String secret) throws ConfigEncry
129129
if (secret.getBytes(StandardCharsets.UTF_8).length > 190) {
130130
throw new ConfigEncryptionException("Secret value is too large. Maximum of 190 bytes is allowed.");
131131
}
132-
133132
try {
134-
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
135-
cipher.init(Cipher.ENCRYPT_MODE, key);
136-
byte[] encrypted = cipher.doFinal(secret.getBytes(StandardCharsets.UTF_8));
137-
return Base64.getEncoder().encodeToString(encrypted);
133+
Base64Value value = Base64Value.create(secret);
134+
return AsymmetricCipher.encrypt(AsymmetricCipher.ALGORITHM_RSA_ECB_OAEP256, null, key, value).toBase64();
138135
} catch (Exception e) {
139136
throw new ConfigEncryptionException("Failed to encrypt using RSA key", e);
140137
}
@@ -170,21 +167,16 @@ public static String encryptAesBytes(char[] masterPassword, byte[] secret) throw
170167

171168
byte[] salt = SECURE_RANDOM.get().generateSeed(SALT_LENGTH);
172169
byte[] nonce = SECURE_RANDOM.get().generateSeed(NONCE_LENGTH);
173-
174-
Cipher cipher = cipher(masterPassword, salt, nonce, Cipher.ENCRYPT_MODE);
175-
// encrypt
176-
byte[] encryptedMessageBytes;
177-
try {
178-
encryptedMessageBytes = cipher.doFinal(secret);
179-
} catch (Exception e) {
180-
throw new ConfigEncryptionException("Failed to encrypt", e);
181-
}
170+
byte[] key = PasswordKeyDerivation
171+
.deriveKey("PBKDF2WithHmacSHA256", null, masterPassword, salt, HASH_ITERATIONS, KEY_LENGTH);
172+
byte[] encrypted = SymmetricCipher.encrypt(SymmetricCipher.ALGORITHM_AES_GCM, key, nonce, Base64Value.create(secret))
173+
.toBytes();
182174

183175
// get bytes to base64 (salt + nonce + encrypted message)
184-
byte[] bytesToEncode = new byte[encryptedMessageBytes.length + salt.length + nonce.length];
176+
byte[] bytesToEncode = new byte[encrypted.length + salt.length + nonce.length];
185177
System.arraycopy(salt, 0, bytesToEncode, 0, salt.length);
186178
System.arraycopy(nonce, 0, bytesToEncode, salt.length, nonce.length);
187-
System.arraycopy(encryptedMessageBytes, 0, bytesToEncode, nonce.length + salt.length, encryptedMessageBytes.length);
179+
System.arraycopy(encrypted, 0, bytesToEncode, nonce.length + salt.length, encrypted.length);
188180

189181
return Base64.getEncoder().encodeToString(bytesToEncode);
190182
}
@@ -203,21 +195,6 @@ private static Cipher cipherLegacy(char[] masterPassword, byte[] salt, int ciphe
203195
}
204196
}
205197

206-
private static Cipher cipher(char[] masterPassword, byte[] salt, byte[] nonce, int cipherMode)
207-
throws ConfigEncryptionException {
208-
try {
209-
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
210-
KeySpec keySpec = new PBEKeySpec(masterPassword, salt, HASH_ITERATIONS, KEY_LENGTH);
211-
SecretKeySpec spec = new SecretKeySpec(secretKeyFactory.generateSecret(keySpec).getEncoded(), "AES");
212-
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
213-
cipher.init(cipherMode, spec, new GCMParameterSpec(AUTHENTICATION_TAG_LENGTH, nonce));
214-
215-
return cipher;
216-
} catch (Exception e) {
217-
throw new ConfigEncryptionException("Failed to prepare a cipher instance", e);
218-
}
219-
}
220-
221198
/**
222199
* Decrypt using legacy AES.
223200
* Will only decrypt messages encrypted with previously used AES method.
@@ -300,16 +277,10 @@ public static byte[] decryptAesBytes(char[] masterPassword, String encryptedBase
300277
System.arraycopy(decodedBytes, SALT_LENGTH, nonce, 0, NONCE_LENGTH);
301278
System.arraycopy(decodedBytes, SALT_LENGTH + NONCE_LENGTH, encryptedBytes, 0, encryptedBytes.length);
302279

303-
// get cipher
304-
Cipher cipher = cipher(masterPassword, salt, nonce, Cipher.DECRYPT_MODE);
305-
306-
// bytes with seed
307-
byte[] decryptedBytes;
308-
decryptedBytes = cipher.doFinal(encryptedBytes);
309-
byte[] originalBytes = new byte[decryptedBytes.length];
310-
System.arraycopy(decryptedBytes, 0, originalBytes, 0, originalBytes.length);
311-
312-
return originalBytes;
280+
byte[] key = PasswordKeyDerivation
281+
.deriveKey("PBKDF2WithHmacSHA256", null, masterPassword, salt, HASH_ITERATIONS, KEY_LENGTH);
282+
Base64Value encryptedValue = Base64Value.create(encryptedBytes);
283+
return SymmetricCipher.decrypt(SymmetricCipher.ALGORITHM_AES_GCM, key, nonce, encryptedValue).toBytes();
313284
} catch (Throwable e) {
314285
throw new ConfigEncryptionException("Failed to decrypt value using AES. Returning clear text value as is: "
315286
+ encryptedBase64, e);

config/encryption/src/main/java/module-info.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
// for RSA encrypted keys
2424
requires transitive io.helidon.common.pki;
25+
requires transitive io.helidon.common.crypto;
2526
requires transitive io.helidon.config;
2627

2728
requires static io.helidon.config.mp;

config/encryption/src/test/java/io/helidon/config/encryption/EncryptionUtilTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
package io.helidon.config.encryption;
1818

1919
import java.nio.charset.StandardCharsets;
20-
import java.security.InvalidKeyException;
2120
import java.security.KeyPair;
2221
import java.security.KeyPairGenerator;
2322
import java.security.NoSuchAlgorithmException;
@@ -26,6 +25,7 @@
2625
import java.util.Base64;
2726

2827
import io.helidon.common.configurable.Resource;
28+
import io.helidon.common.crypto.CryptoException;
2929
import io.helidon.common.pki.KeyConfig;
3030

3131
import org.junit.jupiter.api.BeforeAll;
@@ -77,7 +77,7 @@ public void testEncryptWrongKey() throws NoSuchAlgorithmException {
7777
Throwable cause = e.getCause();
7878
//our message
7979
assertEquals("Failed to encrypt using RSA key", e.getMessage());
80-
assertSame(InvalidKeyException.class, cause.getClass());
80+
assertSame(CryptoException.class, cause.getClass());
8181
}
8282
}
8383

integrations/oci/connect/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@
3939
<groupId>io.helidon.common</groupId>
4040
<artifactId>helidon-common</artifactId>
4141
</dependency>
42+
<dependency>
43+
<groupId>io.helidon.common</groupId>
44+
<artifactId>helidon-common-crypto</artifactId>
45+
</dependency>
4246
<dependency>
4347
<groupId>io.helidon.config</groupId>
4448
<artifactId>helidon-config</artifactId>

integrations/oci/connect/src/main/java/io/helidon/integrations/oci/connect/OciConfigInstancePrincipal.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
import java.net.URI;
2222
import java.nio.charset.StandardCharsets;
2323
import java.security.KeyPair;
24-
import java.security.MessageDigest;
2524
import java.security.PrivateKey;
2625
import java.security.PublicKey;
2726
import java.security.cert.CertificateEncodingException;
@@ -52,8 +51,10 @@
5251
import javax.json.JsonObject;
5352
import javax.json.JsonWriterFactory;
5453

54+
import io.helidon.common.Base64Value;
5555
import io.helidon.common.Version;
5656
import io.helidon.common.configurable.Resource;
57+
import io.helidon.common.crypto.HashDigest;
5758
import io.helidon.common.http.Http;
5859
import io.helidon.common.http.MediaType;
5960
import io.helidon.common.pki.KeyConfig;
@@ -77,6 +78,7 @@ public class OciConfigInstancePrincipal implements OciConfigProvider {
7778
private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Map.of());
7879
private static final JsonWriterFactory JSON_WRITER_FACTORY = Json.createWriterFactory(Map.of());
7980
private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
81+
private static final HashDigest HASH_DIGEST = HashDigest.create(HashDigest.ALGORITHM_SHA_256);
8082

8183
private final AtomicReference<OciSignatureData> currentSignatureData = new AtomicReference<>();
8284
private final String region;
@@ -144,10 +146,9 @@ public static OciConfigInstancePrincipal create() {
144146

145147
private static String fingerprint(X509Certificate leafCertificate) {
146148
try {
147-
byte[] encoded = leafCertificate.getEncoded();
148-
MessageDigest md = MessageDigest.getInstance("SHA-256");
149-
byte[] digest = md.digest(encoded);
150-
return hexEncode(digest, ":");
149+
Base64Value encoded = Base64Value.create(leafCertificate.getEncoded());
150+
Base64Value digest = HASH_DIGEST.digest(encoded);
151+
return hexEncode(digest.toBytes(), ":");
151152
} catch (Exception e) {
152153
throw new OciApiException("Failed to get certificate fingerprint " + leafCertificate, e);
153154
}
@@ -283,7 +284,7 @@ private Single<OciSignatureData> getData(KeyPair keyPair,
283284
JSON_WRITER_FACTORY.createWriter(baos).write(jsonRequest);
284285

285286
byte[] requestBytes = baos.toByteArray();
286-
String sha256 = OciRestApi.computeSha256(requestBytes);
287+
String sha256 = HASH_DIGEST.digest(Base64Value.create(requestBytes)).toBase64();
287288

288289
return federationClient.post()
289290
.path("/v1/x509")

integrations/oci/connect/src/main/java/io/helidon/integrations/oci/connect/OciHttpSignature.java

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,18 @@
1818

1919
import java.net.URI;
2020
import java.nio.charset.StandardCharsets;
21-
import java.security.InvalidKeyException;
22-
import java.security.NoSuchAlgorithmException;
23-
import java.security.Signature;
24-
import java.security.SignatureException;
2521
import java.security.interfaces.RSAPrivateKey;
2622
import java.time.ZoneId;
2723
import java.time.ZonedDateTime;
2824
import java.time.format.DateTimeFormatter;
29-
import java.util.Base64;
3025
import java.util.LinkedList;
3126
import java.util.List;
3227
import java.util.Map;
3328
import java.util.logging.Level;
3429
import java.util.logging.Logger;
3530

31+
import io.helidon.common.Base64Value;
32+
import io.helidon.common.crypto.Signature;
3633
import io.helidon.security.SecurityEnvironment;
3734
import io.helidon.security.providers.httpsign.HttpSignatureException;
3835
import io.helidon.security.providers.httpsign.SignedHeadersConfig;
@@ -49,8 +46,6 @@ class OciHttpSignature {
4946
private final List<String> headers;
5047
private String base64Signature;
5148

52-
private byte[] signatureBytes;
53-
5449
OciHttpSignature(String keyId, List<String> headers) {
5550
this.keyId = keyId;
5651
this.headers = headers;
@@ -60,11 +55,10 @@ static OciHttpSignature sign(SignatureRequest request) {
6055
OciHttpSignature signature = new OciHttpSignature(request.keyId,
6156
request.headersToSign);
6257

63-
signature.signatureBytes = signature.signRsaSha256(request.env,
64-
request.privateKey,
65-
request.newHeaders);
66-
67-
signature.base64Signature = Base64.getEncoder().encodeToString(signature.signatureBytes);
58+
Base64Value signedValue = signature.signRsaSha256(request.env,
59+
request.privateKey,
60+
request.newHeaders);
61+
signature.base64Signature = signedValue.toBase64();
6862

6963
return signature;
7064
}
@@ -76,15 +70,13 @@ String toSignatureHeader() {
7670
+ "signature=\"" + base64Signature + "\"";
7771
}
7872

79-
private byte[] signRsaSha256(SecurityEnvironment env, RSAPrivateKey privateKey, Map<String, List<String>> newHeaders) {
80-
try {
81-
Signature signature = Signature.getInstance("SHA256withRSA");
82-
signature.initSign(privateKey);
83-
signature.update(getBytesToSign(env, newHeaders));
84-
return signature.sign();
85-
} catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
86-
throw new HttpSignatureException(e);
87-
}
73+
private Base64Value signRsaSha256(SecurityEnvironment env, RSAPrivateKey privateKey, Map<String, List<String>> newHeaders) {
74+
Signature signature = Signature.builder()
75+
.privateKey(privateKey)
76+
.algorithm(Signature.ALGORITHM_SHA256_RSA)
77+
.build();
78+
79+
return signature.digest(Base64Value.create(getBytesToSign(env, newHeaders)));
8880
}
8981

9082
private byte[] getBytesToSign(SecurityEnvironment env, Map<String, List<String>> newHeaders) {

0 commit comments

Comments
 (0)