From f1913a5f63bfff5cc2b4dbdb6ff946e6afcece1e Mon Sep 17 00:00:00 2001 From: Marcus Hert Da Coregio Date: Wed, 27 Dec 2023 10:45:59 -0300 Subject: [PATCH] Migrate spring-security-rsa into spring-security-crypto Closes gh-14202 --- crypto/spring-security-crypto.gradle | 3 +- .../crypto/encrypt/KeyStoreKeyFactory.java | 96 ++++++ .../security/crypto/encrypt/RsaAlgorithm.java | 44 +++ .../security/crypto/encrypt/RsaKeyHelper.java | 284 ++++++++++++++++++ .../security/crypto/encrypt/RsaKeyHolder.java | 27 ++ .../crypto/encrypt/RsaRawEncryptor.java | 168 +++++++++++ .../crypto/encrypt/RsaSecretEncryptor.java | 247 +++++++++++++++ .../encrypt/KeyStoreKeyFactoryTests.java | 67 +++++ .../crypto/encrypt/RsaKeyHelperTests.java | 64 ++++ .../crypto/encrypt/RsaRawEncryptorTests.java | 154 ++++++++++ .../encrypt/RsaSecretEncryptorTests.java | 121 ++++++++ crypto/src/test/resources/bad.pem | 2 + crypto/src/test/resources/fake.pem | 15 + crypto/src/test/resources/keystore.jks | Bin 0 -> 3174 bytes crypto/src/test/resources/keystore.pkcs12 | Bin 0 -> 3589 bytes crypto/src/test/resources/spacey.pem | 25 ++ 16 files changed, 1316 insertions(+), 1 deletion(-) create mode 100644 crypto/src/main/java/org/springframework/security/crypto/encrypt/KeyStoreKeyFactory.java create mode 100644 crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaAlgorithm.java create mode 100644 crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaKeyHelper.java create mode 100644 crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaKeyHolder.java create mode 100644 crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaRawEncryptor.java create mode 100644 crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaSecretEncryptor.java create mode 100644 crypto/src/test/java/org/springframework/security/crypto/encrypt/KeyStoreKeyFactoryTests.java create mode 100644 crypto/src/test/java/org/springframework/security/crypto/encrypt/RsaKeyHelperTests.java create mode 100644 crypto/src/test/java/org/springframework/security/crypto/encrypt/RsaRawEncryptorTests.java create mode 100644 crypto/src/test/java/org/springframework/security/crypto/encrypt/RsaSecretEncryptorTests.java create mode 100644 crypto/src/test/resources/bad.pem create mode 100644 crypto/src/test/resources/fake.pem create mode 100644 crypto/src/test/resources/keystore.jks create mode 100644 crypto/src/test/resources/keystore.pkcs12 create mode 100644 crypto/src/test/resources/spacey.pem diff --git a/crypto/spring-security-crypto.gradle b/crypto/spring-security-crypto.gradle index b45a20b0d87..0894300dbcd 100644 --- a/crypto/spring-security-crypto.gradle +++ b/crypto/spring-security-crypto.gradle @@ -3,8 +3,9 @@ apply plugin: 'io.spring.convention.spring-module' dependencies { management platform(project(":spring-security-dependencies")) optional 'org.springframework:spring-jcl' + optional 'org.springframework:spring-core' optional 'org.bouncycastle:bcpkix-jdk15on' - + testImplementation "org.assertj:assertj-core" testImplementation "org.junit.jupiter:junit-jupiter-api" testImplementation "org.junit.jupiter:junit-jupiter-params" diff --git a/crypto/src/main/java/org/springframework/security/crypto/encrypt/KeyStoreKeyFactory.java b/crypto/src/main/java/org/springframework/security/crypto/encrypt/KeyStoreKeyFactory.java new file mode 100644 index 00000000000..49a3072b55d --- /dev/null +++ b/crypto/src/main/java/org/springframework/security/crypto/encrypt/KeyStoreKeyFactory.java @@ -0,0 +1,96 @@ +/* + * Copyright 2013-2023 the original author or authors. + * + * Licensed 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 + * + * https://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.springframework.security.crypto.encrypt; + +import java.io.InputStream; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyStore; +import java.security.PublicKey; +import java.security.cert.Certificate; +import java.security.interfaces.RSAPrivateCrtKey; +import java.security.spec.RSAPublicKeySpec; + +import org.springframework.core.io.Resource; +import org.springframework.util.StringUtils; + +/** + * @author Dave Syer + * @author Tim Ysewyn + * @since 6.3 + */ +public class KeyStoreKeyFactory { + + private final Resource resource; + + private final char[] password; + + private KeyStore store; + + private final Object lock = new Object(); + + private final String type; + + public KeyStoreKeyFactory(Resource resource, char[] password) { + this(resource, password, type(resource)); + } + + private static String type(Resource resource) { + String ext = StringUtils.getFilenameExtension(resource.getFilename()); + return (ext != null) ? ext : "jks"; + } + + public KeyStoreKeyFactory(Resource resource, char[] password, String type) { + this.resource = resource; + this.password = password; + this.type = type; + } + + public KeyPair getKeyPair(String alias) { + return getKeyPair(alias, this.password); + } + + public KeyPair getKeyPair(String alias, char[] password) { + try { + synchronized (this.lock) { + if (this.store == null) { + synchronized (this.lock) { + this.store = KeyStore.getInstance(this.type); + try (InputStream stream = this.resource.getInputStream()) { + this.store.load(stream, this.password); + } + } + } + } + RSAPrivateCrtKey key = (RSAPrivateCrtKey) this.store.getKey(alias, password); + Certificate certificate = this.store.getCertificate(alias); + PublicKey publicKey = null; + if (certificate != null) { + publicKey = certificate.getPublicKey(); + } + else if (key != null) { + RSAPublicKeySpec spec = new RSAPublicKeySpec(key.getModulus(), key.getPublicExponent()); + publicKey = KeyFactory.getInstance("RSA").generatePublic(spec); + } + return new KeyPair(publicKey, key); + } + catch (Exception ex) { + throw new IllegalStateException("Cannot load keys from store: " + this.resource, ex); + } + } + +} diff --git a/crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaAlgorithm.java b/crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaAlgorithm.java new file mode 100644 index 00000000000..f7654553300 --- /dev/null +++ b/crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaAlgorithm.java @@ -0,0 +1,44 @@ +/* + * Copyright 2013-2023 the original author or authors. + * + * Licensed 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 + * + * https://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.springframework.security.crypto.encrypt; + +/** + * @author Dave Syer + * @since 6.3 + */ +public enum RsaAlgorithm { + + DEFAULT("RSA", 117), OAEP("RSA/ECB/OAEPPadding", 86); + + private final String name; + + private final int maxLength; + + RsaAlgorithm(String name, int maxLength) { + this.name = name; + this.maxLength = maxLength; + } + + public String getJceName() { + return this.name; + } + + public int getMaxLength() { + return this.maxLength; + } + +} diff --git a/crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaKeyHelper.java b/crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaKeyHelper.java new file mode 100644 index 00000000000..732c988046a --- /dev/null +++ b/crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaKeyHelper.java @@ -0,0 +1,284 @@ +/* + * Copyright 2013-2023 the original author or authors. + * + * Licensed 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 + * + * https://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.springframework.security.crypto.encrypt; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.StringWriter; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.security.spec.RSAPrivateCrtKeySpec; +import java.security.spec.RSAPublicKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Arrays; +import java.util.Base64; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.bouncycastle.asn1.ASN1Sequence; + +/** + * Reads RSA key pairs using BC provider classes but without the need to specify a crypto + * provider or have BC added as one. + * + * @author Luke Taylor + * @author Dave Syer + */ +final class RsaKeyHelper { + + private static final Charset UTF8 = StandardCharsets.UTF_8; + + private static final String BEGIN = "-----BEGIN"; + + private static final Pattern PEM_DATA = Pattern.compile(".*-----BEGIN (.*)-----(.*)-----END (.*)-----", + Pattern.DOTALL); + + private static final byte[] PREFIX = new byte[] { 0, 0, 0, 7, 's', 's', 'h', '-', 'r', 's', 'a' }; + + private RsaKeyHelper() { + } + + static KeyPair parseKeyPair(String pemData) { + Matcher m = PEM_DATA.matcher(pemData.replaceAll("\n *", "").trim()); + + if (!m.matches()) { + try { + RSAPublicKey publicValue = extractPublicKey(pemData); + if (publicValue != null) { + return new KeyPair(publicValue, null); + } + } + catch (Exception ex) { + // Ignore + } + throw new IllegalArgumentException("String is not PEM encoded data, nor a public key encoded for ssh"); + } + + String type = m.group(1); + final byte[] content = base64Decode(m.group(2)); + + PublicKey publicKey; + PrivateKey privateKey = null; + + try { + KeyFactory fact = KeyFactory.getInstance("RSA"); + switch (type) { + case "RSA PRIVATE KEY" -> { + ASN1Sequence seq = ASN1Sequence.getInstance(content); + if (seq.size() != 9) { + throw new IllegalArgumentException("Invalid RSA Private Key ASN1 sequence."); + } + org.bouncycastle.asn1.pkcs.RSAPrivateKey key = org.bouncycastle.asn1.pkcs.RSAPrivateKey + .getInstance(seq); + RSAPublicKeySpec pubSpec = new RSAPublicKeySpec(key.getModulus(), key.getPublicExponent()); + RSAPrivateCrtKeySpec privSpec = new RSAPrivateCrtKeySpec(key.getModulus(), key.getPublicExponent(), + key.getPrivateExponent(), key.getPrime1(), key.getPrime2(), key.getExponent1(), + key.getExponent2(), key.getCoefficient()); + publicKey = fact.generatePublic(pubSpec); + privateKey = fact.generatePrivate(privSpec); + } + case "PUBLIC KEY" -> { + KeySpec keySpec = new X509EncodedKeySpec(content); + publicKey = fact.generatePublic(keySpec); + } + case "RSA PUBLIC KEY" -> { + ASN1Sequence seq = ASN1Sequence.getInstance(content); + org.bouncycastle.asn1.pkcs.RSAPublicKey key = org.bouncycastle.asn1.pkcs.RSAPublicKey + .getInstance(seq); + RSAPublicKeySpec pubSpec = new RSAPublicKeySpec(key.getModulus(), key.getPublicExponent()); + publicKey = fact.generatePublic(pubSpec); + } + default -> throw new IllegalArgumentException(type + " is not a supported format"); + } + + return new KeyPair(publicKey, privateKey); + } + catch (InvalidKeySpecException ex) { + throw new RuntimeException(ex); + } + catch (NoSuchAlgorithmException ex) { + throw new IllegalStateException(ex); + } + } + + private static byte[] base64Decode(String string) { + try { + ByteBuffer bytes = UTF8.newEncoder().encode(CharBuffer.wrap(string)); + byte[] bytesCopy = new byte[bytes.limit()]; + System.arraycopy(bytes.array(), 0, bytesCopy, 0, bytes.limit()); + return Base64.getDecoder().decode(bytesCopy); + } + catch (CharacterCodingException ex) { + throw new RuntimeException(ex); + } + } + + static String base64Encode(byte[] bytes) { + try { + return UTF8.newDecoder().decode(ByteBuffer.wrap(Base64.getEncoder().encode(bytes))).toString(); + } + catch (CharacterCodingException ex) { + throw new RuntimeException(ex); + } + } + + static KeyPair generateKeyPair() { + try { + final KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); + keyGen.initialize(1024); + return keyGen.generateKeyPair(); + } + catch (NoSuchAlgorithmException ex) { + throw new IllegalStateException(ex); + } + + } + + private static final Pattern SSH_PUB_KEY = Pattern.compile("ssh-(rsa|dsa) ([A-Za-z0-9/+]+=*) (.*)"); + + private static RSAPublicKey extractPublicKey(String key) { + + Matcher m = SSH_PUB_KEY.matcher(key); + + if (m.matches()) { + String alg = m.group(1); + String encKey = m.group(2); + // String id = m.group(3); + + if (!"rsa".equalsIgnoreCase(alg)) { + throw new IllegalArgumentException("Only RSA is currently supported, but algorithm was " + alg); + } + + return parseSSHPublicKey(encKey); + } + else if (!key.startsWith(BEGIN)) { + // Assume it's the plain Base64 encoded ssh key without the + // "ssh-rsa" at the start + return parseSSHPublicKey(key); + } + + return null; + } + + static RSAPublicKey parsePublicKey(String key) { + + RSAPublicKey publicKey = extractPublicKey(key); + + if (publicKey != null) { + return publicKey; + } + + KeyPair kp = parseKeyPair(key); + + if (kp.getPublic() == null) { + throw new IllegalArgumentException("Key data does not contain a public key"); + } + + return (RSAPublicKey) kp.getPublic(); + + } + + static String encodePublicKey(RSAPublicKey key, String id) { + StringWriter output = new StringWriter(); + output.append("ssh-rsa "); + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + try { + stream.write(PREFIX); + writeBigInteger(stream, key.getPublicExponent()); + writeBigInteger(stream, key.getModulus()); + } + catch (IOException ex) { + throw new IllegalStateException("Cannot encode key", ex); + } + output.append(base64Encode(stream.toByteArray())); + output.append(" " + id); + return output.toString(); + } + + private static RSAPublicKey parseSSHPublicKey(String encKey) { + ByteArrayInputStream in = new ByteArrayInputStream(base64Decode(encKey)); + + byte[] prefix = new byte[11]; + + try { + if (in.read(prefix) != 11 || !Arrays.equals(PREFIX, prefix)) { + throw new IllegalArgumentException("SSH key prefix not found"); + } + + BigInteger e = new BigInteger(readBigInteger(in)); + BigInteger n = new BigInteger(readBigInteger(in)); + + return createPublicKey(n, e); + } + catch (IOException ex) { + throw new RuntimeException(ex); + } + } + + static RSAPublicKey createPublicKey(BigInteger n, BigInteger e) { + try { + return (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new RSAPublicKeySpec(n, e)); + } + catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + private static void writeBigInteger(ByteArrayOutputStream stream, BigInteger num) throws IOException { + int length = num.toByteArray().length; + byte[] data = new byte[4]; + data[0] = (byte) ((length >> 24) & 0xFF); + data[1] = (byte) ((length >> 16) & 0xFF); + data[2] = (byte) ((length >> 8) & 0xFF); + data[3] = (byte) (length & 0xFF); + stream.write(data); + stream.write(num.toByteArray()); + } + + private static byte[] readBigInteger(ByteArrayInputStream in) throws IOException { + byte[] b = new byte[4]; + + if (in.read(b) != 4) { + throw new IOException("Expected length data as 4 bytes"); + } + + int l = ((b[0] & 0xFF) << 24) | ((b[1] & 0xFF) << 16) | ((b[2] & 0xFF) << 8) | (b[3] & 0xFF); + + b = new byte[l]; + + if (in.read(b) != l) { + throw new IOException("Expected " + l + " key bytes"); + } + + return b; + } + +} diff --git a/crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaKeyHolder.java b/crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaKeyHolder.java new file mode 100644 index 00000000000..881942a7c63 --- /dev/null +++ b/crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaKeyHolder.java @@ -0,0 +1,27 @@ +/* + * Copyright 2013-2023 the original author or authors. + * + * Licensed 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 + * + * https://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.springframework.security.crypto.encrypt; + +/** + * @author Dave Syer + * @since 6.3 + */ +public interface RsaKeyHolder { + + String getPublicKey(); + +} diff --git a/crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaRawEncryptor.java b/crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaRawEncryptor.java new file mode 100644 index 00000000000..b5ee9d6b969 --- /dev/null +++ b/crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaRawEncryptor.java @@ -0,0 +1,168 @@ +/* + * Copyright 2013-2023 the original author or authors. + * + * Licensed 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 + * + * https://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.springframework.security.crypto.encrypt; + +import java.io.ByteArrayOutputStream; +import java.nio.charset.Charset; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.interfaces.RSAKey; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.util.Base64; + +import javax.crypto.Cipher; + +/** + * @author Dave Syer + * @since 6.3 + */ +public class RsaRawEncryptor implements BytesEncryptor, TextEncryptor, RsaKeyHolder { + + private static final String DEFAULT_ENCODING = "UTF-8"; + + private RsaAlgorithm algorithm = RsaAlgorithm.DEFAULT; + + private Charset charset; + + private RSAPublicKey publicKey; + + private RSAPrivateKey privateKey; + + private Charset defaultCharset; + + public RsaRawEncryptor(RsaAlgorithm algorithm) { + this(RsaKeyHelper.generateKeyPair(), algorithm); + } + + public RsaRawEncryptor() { + this(RsaKeyHelper.generateKeyPair()); + } + + public RsaRawEncryptor(KeyPair keyPair, RsaAlgorithm algorithm) { + this(DEFAULT_ENCODING, keyPair.getPublic(), keyPair.getPrivate(), algorithm); + } + + public RsaRawEncryptor(KeyPair keyPair) { + this(DEFAULT_ENCODING, keyPair.getPublic(), keyPair.getPrivate()); + } + + public RsaRawEncryptor(String pemData) { + this(RsaKeyHelper.parseKeyPair(pemData)); + } + + public RsaRawEncryptor(PublicKey publicKey) { + this(DEFAULT_ENCODING, publicKey, null); + } + + public RsaRawEncryptor(String encoding, PublicKey publicKey, PrivateKey privateKey) { + this(encoding, publicKey, privateKey, RsaAlgorithm.DEFAULT); + } + + public RsaRawEncryptor(String encoding, PublicKey publicKey, PrivateKey privateKey, RsaAlgorithm algorithm) { + this.charset = Charset.forName(encoding); + this.publicKey = (RSAPublicKey) publicKey; + this.privateKey = (RSAPrivateKey) privateKey; + this.defaultCharset = Charset.forName(DEFAULT_ENCODING); + this.algorithm = algorithm; + } + + @Override + public String getPublicKey() { + return RsaKeyHelper.encodePublicKey(this.publicKey, "application"); + } + + @Override + public String encrypt(String text) { + return new String(Base64.getEncoder().encode(encrypt(text.getBytes(this.charset))), this.defaultCharset); + } + + @Override + public String decrypt(String encryptedText) { + if (this.privateKey == null) { + throw new IllegalStateException("Private key must be provided for decryption"); + } + return new String(decrypt(Base64.getDecoder().decode(encryptedText.getBytes(this.defaultCharset))), + this.charset); + } + + @Override + public byte[] encrypt(byte[] byteArray) { + return encrypt(byteArray, this.publicKey, this.algorithm); + } + + @Override + public byte[] decrypt(byte[] encryptedByteArray) { + return decrypt(encryptedByteArray, this.privateKey, this.algorithm); + } + + private static byte[] encrypt(byte[] text, PublicKey key, RsaAlgorithm alg) { + ByteArrayOutputStream output = new ByteArrayOutputStream(text.length); + try { + final Cipher cipher = Cipher.getInstance(alg.getJceName()); + int limit = Math.min(text.length, alg.getMaxLength()); + int pos = 0; + while (pos < text.length) { + cipher.init(Cipher.ENCRYPT_MODE, key); + cipher.update(text, pos, limit); + pos += limit; + limit = Math.min(text.length - pos, alg.getMaxLength()); + byte[] buffer = cipher.doFinal(); + output.write(buffer, 0, buffer.length); + } + return output.toByteArray(); + } + catch (RuntimeException ex) { + throw ex; + } + catch (Exception ex) { + throw new IllegalStateException("Cannot encrypt", ex); + } + } + + private static byte[] decrypt(byte[] text, RSAPrivateKey key, RsaAlgorithm alg) { + ByteArrayOutputStream output = new ByteArrayOutputStream(text.length); + try { + final Cipher cipher = Cipher.getInstance(alg.getJceName()); + int maxLength = getByteLength(key); + int pos = 0; + while (pos < text.length) { + int limit = Math.min(text.length - pos, maxLength); + cipher.init(Cipher.DECRYPT_MODE, key); + cipher.update(text, pos, limit); + pos += limit; + byte[] buffer = cipher.doFinal(); + output.write(buffer, 0, buffer.length); + } + return output.toByteArray(); + } + catch (RuntimeException ex) { + throw ex; + } + catch (Exception ex) { + throw new IllegalStateException("Cannot decrypt", ex); + } + } + + // copied from sun.security.rsa.RSACore.getByteLength(java.math.BigInteger) + public static int getByteLength(RSAKey key) { + int n = key.getModulus().bitLength(); + return (n + 7) >> 3; + } + +} diff --git a/crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaSecretEncryptor.java b/crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaSecretEncryptor.java new file mode 100644 index 00000000000..3b5a3bdf6ff --- /dev/null +++ b/crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaSecretEncryptor.java @@ -0,0 +1,247 @@ +/* + * Copyright 2013-2023 the original author or authors. + * + * Licensed 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 + * + * https://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.springframework.security.crypto.encrypt; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.Charset; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.interfaces.RSAPublicKey; +import java.util.Base64; + +import javax.crypto.Cipher; + +import org.springframework.security.crypto.codec.Hex; +import org.springframework.security.crypto.keygen.KeyGenerators; + +/** + * @author Dave Syer + * @since 6.3 + */ +public class RsaSecretEncryptor implements BytesEncryptor, TextEncryptor, RsaKeyHolder { + + private static final String DEFAULT_ENCODING = "UTF-8"; + + // The secret for encryption is random (so dictionary attack is not a danger) + private static final String DEFAULT_SALT = "deadbeef"; + + private final String salt; + + private RsaAlgorithm algorithm = RsaAlgorithm.DEFAULT; + + private final Charset charset; + + private final PublicKey publicKey; + + private final PrivateKey privateKey; + + private final Charset defaultCharset; + + private final boolean gcm; + + public RsaSecretEncryptor(RsaAlgorithm algorithm, String salt, boolean gcm) { + this(RsaKeyHelper.generateKeyPair(), algorithm, salt, gcm); + } + + public RsaSecretEncryptor(RsaAlgorithm algorithm, String salt) { + this(RsaKeyHelper.generateKeyPair(), algorithm, salt); + } + + public RsaSecretEncryptor(RsaAlgorithm algorithm, boolean gcm) { + this(RsaKeyHelper.generateKeyPair(), algorithm, DEFAULT_SALT, gcm); + } + + public RsaSecretEncryptor(RsaAlgorithm algorithm) { + this(RsaKeyHelper.generateKeyPair(), algorithm); + } + + public RsaSecretEncryptor() { + this(RsaKeyHelper.generateKeyPair()); + } + + public RsaSecretEncryptor(KeyPair keyPair, RsaAlgorithm algorithm, String salt, boolean gcm) { + this(DEFAULT_ENCODING, keyPair.getPublic(), keyPair.getPrivate(), algorithm, salt, gcm); + } + + public RsaSecretEncryptor(KeyPair keyPair, RsaAlgorithm algorithm, String salt) { + this(DEFAULT_ENCODING, keyPair.getPublic(), keyPair.getPrivate(), algorithm, salt, false); + } + + public RsaSecretEncryptor(KeyPair keyPair, RsaAlgorithm algorithm) { + this(DEFAULT_ENCODING, keyPair.getPublic(), keyPair.getPrivate(), algorithm); + } + + public RsaSecretEncryptor(KeyPair keyPair) { + this(DEFAULT_ENCODING, keyPair.getPublic(), keyPair.getPrivate()); + } + + public RsaSecretEncryptor(String pemData, RsaAlgorithm algorithm, String salt) { + this(RsaKeyHelper.parseKeyPair(pemData), algorithm, salt); + } + + public RsaSecretEncryptor(String pemData, RsaAlgorithm algorithm) { + this(RsaKeyHelper.parseKeyPair(pemData), algorithm); + } + + public RsaSecretEncryptor(String pemData) { + this(RsaKeyHelper.parseKeyPair(pemData)); + } + + public RsaSecretEncryptor(PublicKey publicKey, RsaAlgorithm algorithm, String salt, boolean gcm) { + this(DEFAULT_ENCODING, publicKey, null, algorithm, salt, gcm); + } + + public RsaSecretEncryptor(PublicKey publicKey, RsaAlgorithm algorithm, String salt) { + this(DEFAULT_ENCODING, publicKey, null, algorithm, salt, false); + } + + public RsaSecretEncryptor(PublicKey publicKey, RsaAlgorithm algorithm) { + this(DEFAULT_ENCODING, publicKey, null, algorithm); + } + + public RsaSecretEncryptor(PublicKey publicKey) { + this(DEFAULT_ENCODING, publicKey, null); + } + + public RsaSecretEncryptor(String encoding, PublicKey publicKey, PrivateKey privateKey) { + this(encoding, publicKey, privateKey, RsaAlgorithm.DEFAULT); + } + + public RsaSecretEncryptor(String encoding, PublicKey publicKey, PrivateKey privateKey, RsaAlgorithm algorithm) { + this(encoding, publicKey, privateKey, algorithm, DEFAULT_SALT, false); + } + + public RsaSecretEncryptor(String encoding, PublicKey publicKey, PrivateKey privateKey, RsaAlgorithm algorithm, + String salt, boolean gcm) { + this.charset = Charset.forName(encoding); + this.publicKey = publicKey; + this.privateKey = privateKey; + this.defaultCharset = Charset.forName(DEFAULT_ENCODING); + this.algorithm = algorithm; + this.salt = isHex(salt) ? salt : new String(Hex.encode(salt.getBytes(this.defaultCharset))); + this.gcm = gcm; + } + + @Override + public String getPublicKey() { + return RsaKeyHelper.encodePublicKey((RSAPublicKey) this.publicKey, "application"); + } + + @Override + public String encrypt(String text) { + return new String(Base64.getEncoder().encode(encrypt(text.getBytes(this.charset))), this.defaultCharset); + } + + @Override + public String decrypt(String encryptedText) { + if (!canDecrypt()) { + throw new IllegalStateException("Encryptor is not configured for decryption"); + } + return new String(decrypt(Base64.getDecoder().decode(encryptedText.getBytes(this.defaultCharset))), + this.charset); + } + + @Override + public byte[] encrypt(byte[] byteArray) { + return encrypt(byteArray, this.publicKey, this.algorithm, this.salt, this.gcm); + } + + @Override + public byte[] decrypt(byte[] encryptedByteArray) { + if (!canDecrypt()) { + throw new IllegalStateException("Encryptor is not configured for decryption"); + } + return decrypt(encryptedByteArray, this.privateKey, this.algorithm, this.salt, this.gcm); + } + + private static byte[] encrypt(byte[] text, PublicKey key, RsaAlgorithm alg, String salt, boolean gcm) { + byte[] random = KeyGenerators.secureRandom(16).generateKey(); + BytesEncryptor aes = gcm ? Encryptors.stronger(new String(Hex.encode(random)), salt) + : Encryptors.standard(new String(Hex.encode(random)), salt); + try { + final Cipher cipher = Cipher.getInstance(alg.getJceName()); + cipher.init(Cipher.ENCRYPT_MODE, key); + byte[] secret = cipher.doFinal(random); + ByteArrayOutputStream result = new ByteArrayOutputStream(text.length + 20); + writeInt(result, secret.length); + result.write(secret); + result.write(aes.encrypt(text)); + return result.toByteArray(); + } + catch (RuntimeException ex) { + throw ex; + } + catch (Exception ex) { + throw new IllegalStateException("Cannot encrypt", ex); + } + } + + private static void writeInt(ByteArrayOutputStream result, int length) throws IOException { + byte[] data = new byte[2]; + data[0] = (byte) ((length >> 8) & 0xFF); + data[1] = (byte) (length & 0xFF); + result.write(data); + } + + private static int readInt(ByteArrayInputStream result) throws IOException { + byte[] b = new byte[2]; + result.read(b); + return ((b[0] & 0xFF) << 8) | (b[1] & 0xFF); + } + + private static byte[] decrypt(byte[] text, PrivateKey key, RsaAlgorithm alg, String salt, boolean gcm) { + ByteArrayInputStream input = new ByteArrayInputStream(text); + ByteArrayOutputStream output = new ByteArrayOutputStream(text.length); + try { + int length = readInt(input); + byte[] random = new byte[length]; + input.read(random); + final Cipher cipher = Cipher.getInstance(alg.getJceName()); + cipher.init(Cipher.DECRYPT_MODE, key); + String secret = new String(Hex.encode(cipher.doFinal(random))); + byte[] buffer = new byte[text.length - random.length - 2]; + input.read(buffer); + BytesEncryptor aes = gcm ? Encryptors.stronger(secret, salt) : Encryptors.standard(secret, salt); + output.write(aes.decrypt(buffer)); + return output.toByteArray(); + } + catch (RuntimeException ex) { + throw ex; + } + catch (Exception ex) { + throw new IllegalStateException("Cannot decrypt", ex); + } + } + + private static boolean isHex(String input) { + try { + Hex.decode(input); + return true; + } + catch (Exception ex) { + return false; + } + } + + public boolean canDecrypt() { + return this.privateKey != null; + } + +} diff --git a/crypto/src/test/java/org/springframework/security/crypto/encrypt/KeyStoreKeyFactoryTests.java b/crypto/src/test/java/org/springframework/security/crypto/encrypt/KeyStoreKeyFactoryTests.java new file mode 100644 index 00000000000..d69892470e3 --- /dev/null +++ b/crypto/src/test/java/org/springframework/security/crypto/encrypt/KeyStoreKeyFactoryTests.java @@ -0,0 +1,67 @@ +/* + * Copyright 2013-2023 the original author or authors. + * + * Licensed 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 + * + * https://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.springframework.security.crypto.encrypt; + +import org.junit.jupiter.api.Test; + +import org.springframework.core.io.ClassPathResource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Dave Syer + * + */ +public class KeyStoreKeyFactoryTests { + + @Test + public void initializeEncryptorFromKeyStore() { + char[] password = "foobar".toCharArray(); + KeyStoreKeyFactory factory = new KeyStoreKeyFactory(new ClassPathResource("keystore.jks"), password); + RsaSecretEncryptor encryptor = new RsaSecretEncryptor(factory.getKeyPair("test")); + assertThat(encryptor.canDecrypt()).as("Should be able to decrypt").isTrue(); + assertThat(encryptor.decrypt(encryptor.encrypt("foo"))).isEqualTo("foo"); + } + + @Test + public void initializeEncryptorFromPkcs12KeyStore() { + char[] password = "letmein".toCharArray(); + KeyStoreKeyFactory factory = new KeyStoreKeyFactory(new ClassPathResource("keystore.pkcs12"), password); + RsaSecretEncryptor encryptor = new RsaSecretEncryptor(factory.getKeyPair("mytestkey")); + assertThat(encryptor.canDecrypt()).as("Should be able to decrypt").isTrue(); + assertThat(encryptor.decrypt(encryptor.encrypt("foo"))).isEqualTo("foo"); + } + + @Test + public void initializeEncryptorFromTrustedCertificateInKeyStore() { + char[] password = "foobar".toCharArray(); + KeyStoreKeyFactory factory = new KeyStoreKeyFactory(new ClassPathResource("keystore.jks"), password); + RsaSecretEncryptor encryptor = new RsaSecretEncryptor(factory.getKeyPair("testcertificate")); + assertThat(encryptor.canDecrypt()).as("Should not be able to decrypt").isFalse(); + assertThat(encryptor.encrypt("foo")).isNotEqualTo("foo"); + } + + @Test + public void initializeEncryptorFromTrustedCertificateInPkcs12KeyStore() { + char[] password = "letmein".toCharArray(); + KeyStoreKeyFactory factory = new KeyStoreKeyFactory(new ClassPathResource("keystore.pkcs12"), password); + RsaSecretEncryptor encryptor = new RsaSecretEncryptor(factory.getKeyPair("mytestcertificate")); + assertThat(encryptor.canDecrypt()).as("Should not be able to decrypt").isFalse(); + assertThat(encryptor.encrypt("foo")).isNotEqualTo("foo"); + } + +} diff --git a/crypto/src/test/java/org/springframework/security/crypto/encrypt/RsaKeyHelperTests.java b/crypto/src/test/java/org/springframework/security/crypto/encrypt/RsaKeyHelperTests.java new file mode 100644 index 00000000000..c16687d144d --- /dev/null +++ b/crypto/src/test/java/org/springframework/security/crypto/encrypt/RsaKeyHelperTests.java @@ -0,0 +1,64 @@ +/* + * Copyright 2013-2023 the original author or authors. + * + * Licensed 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 + * + * https://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.springframework.security.crypto.encrypt; + +import java.nio.charset.StandardCharsets; +import java.security.KeyPair; + +import org.junit.jupiter.api.Test; + +import org.springframework.core.io.ClassPathResource; +import org.springframework.util.StreamUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +public class RsaKeyHelperTests { + + @Test + public void parsePrivateKey() throws Exception { + // ssh-keygen -m pem -b 1024 -f src/test/resources/fake.pem + String pem = StreamUtils.copyToString(new ClassPathResource("/fake.pem", getClass()).getInputStream(), + StandardCharsets.UTF_8); + KeyPair result = RsaKeyHelper.parseKeyPair(pem); + assertThat(result.getPrivate().getEncoded().length > 0).isTrue(); + assertThat(result.getPrivate().getAlgorithm()).isEqualTo("RSA"); + } + + @Test + public void parseSpaceyKey() throws Exception { + String pem = StreamUtils.copyToString(new ClassPathResource("/spacey.pem", getClass()).getInputStream(), + StandardCharsets.UTF_8); + KeyPair result = RsaKeyHelper.parseKeyPair(pem); + assertThat(result.getPrivate().getEncoded().length > 0).isTrue(); + assertThat(result.getPrivate().getAlgorithm()).isEqualTo("RSA"); + } + + @Test + public void parseBadKey() throws Exception { + // ssh-keygen -m pem -b 1024 -f src/test/resources/fake.pem + String pem = StreamUtils.copyToString(new ClassPathResource("/bad.pem", getClass()).getInputStream(), + StandardCharsets.UTF_8); + try { + RsaKeyHelper.parseKeyPair(pem); + throw new IllegalStateException("Expected IllegalArgumentException"); + } + catch (IllegalArgumentException ex) { + assertThat(ex.getMessage().contains("PEM")).isTrue(); + } + } + +} diff --git a/crypto/src/test/java/org/springframework/security/crypto/encrypt/RsaRawEncryptorTests.java b/crypto/src/test/java/org/springframework/security/crypto/encrypt/RsaRawEncryptorTests.java new file mode 100644 index 00000000000..c2b3accd388 --- /dev/null +++ b/crypto/src/test/java/org/springframework/security/crypto/encrypt/RsaRawEncryptorTests.java @@ -0,0 +1,154 @@ +/* + * Copyright 2013-2023 the original author or authors. + * + * Licensed 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 + * + * https://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.springframework.security.crypto.encrypt; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Dave Syer + * + */ +public class RsaRawEncryptorTests { + + private RsaRawEncryptor encryptor = new RsaRawEncryptor(); + + @BeforeEach + public void init() { + LONG_STRING = SHORT_STRING + SHORT_STRING + SHORT_STRING + SHORT_STRING; + for (int i = 0; i < 4; i++) { + LONG_STRING = LONG_STRING + LONG_STRING; + } + } + + @Test + public void roundTrip() { + assertThat(this.encryptor.decrypt(this.encryptor.encrypt("encryptor"))).isEqualTo("encryptor"); + } + + @Test + public void roundTripOeap() { + this.encryptor = new RsaRawEncryptor(RsaAlgorithm.OAEP); + assertThat(this.encryptor.decrypt(this.encryptor.encrypt("encryptor"))).isEqualTo("encryptor"); + } + + @Test + public void roundTripLongString() { + assertThat(this.encryptor.decrypt(this.encryptor.encrypt(LONG_STRING))).isEqualTo(LONG_STRING); + } + + @Test + public void roundTripLongStringOeap() { + this.encryptor = new RsaRawEncryptor(RsaAlgorithm.OAEP); + assertThat(this.encryptor.decrypt(this.encryptor.encrypt(LONG_STRING))).isEqualTo(LONG_STRING); + } + + @Test + public void roundTrip2048Key() { + String pemData = "-----BEGIN RSA PRIVATE KEY-----" + + "MIIEpQIBAAKCAQEA5KHEkCudAHCKIUHKyW6Z8dMyQsKrLbpDe0wDzx9MBARcOoS9" + + "ZUjzXwK6p/0RM6aCp+b9kkr37QKQ9K/Am13sr0z8Mkn1Q2cvXiL5gbnY1nYGk8/m" + + "CBX3QEhH2UII4yJsDVx1xmcSorZaWmeNKor7Zl3SZaQpWTvlkMgQKwY8DZL6PPxt" + + "JRPeKmuUY6B59u5okh1G6Y9OnT2dVxAkqT8WgLHu6StxBmueJ272x2sUWUzoDhnP" + + "7JRqa7h7t6fml3o3Op1iCywCOFzCIcK6G/oG/WZ7tbBYkwQdDjn/9VMdKkkPufwq" + + "zt4S75NJygXDwDnNPiTVoaOwrRrL8ahgw6bFCQIDAQABAoIBAECIMHUI+l2fZj2Q" + + "1m4Ym7cYB320eKCFjHqGsCSMDuarXGTgBp1KA/dzS8ASvAI6I3LEzhm2s1fge420" + + "9cZksmOgdSa0nVeTDlmhwY8OJ9gQpDagXas2l/066Zy2+M8zbhAvYsbHXQk0MziF" + + "NeEmLWNtY+9wcINRVrCQ549dSSIDK6UX21oU6d1mrlnF5/bbbdDIM3dKok355jwx" + + "0HFY0tJIs1zArsBVoz3Ccu1MQEfnxEFM1LLPi5rE6cuHIOBinbD1OQ2R/HM2aukG" + + "Rk2m6F3wAieJ7zpt5yaHuuIedn8p8m2NVulXAjgkY2oQl3GGiDH/H7eZlrvQRg6E" + + "D8Bq+ykCgYEA+AfPXVeeVg3Qu0KsNrACek/o92BMY9g3GyPVGULGvq9seoNB86hj" + + "nXasqngBfTlOfJFiahoEzRBB9hIyo1zMw4x99pR8nGxhR3aU+v8EGftMABGHWsB9" + + "Jxj4YQH4fhi57iBa72QmNPbu/1o7y3SEe68E5PJ8KY3jc4xos8Vl658CgYEA6/pk" + + "t6WZII+9lpxQfePQDIlBWAphiQceh995bGXfDmX3vOVmPozix9/fUtF1TeKS/ypw" + + "u++Qmvj5oMsBVrjCyoOYfHKE2vGrLoEzkX/sPO65IsV00geZZoyCEKEE3USJfY46" + + "u0hs61oP8HJyLhLiYiGcFTzZ4nEvvEbiM4E/DlcCgYEA6S0OecZhiK08SpAHrvIR" + + "okN11PqnVkZyqAUr1a+9gI8TAKpdWmA4JlTnRuvDGqLBcsKLLwx+7voVyOyaxpH7" + + "vutZkHNQIw6Q9co5jS4qAPMLJBVWlq7X+eWzvB9KKeG9Cm1IkD4q3Sg4z79Y75D+" + + "6/hCNarxp29JIdwior81bikCgYEApp1P+b7pxGzZPvs1df2hCwjqY0BJJ5goPWVT" + + "dW7kNGVYqz4JmAafpOJz6yTLP2fHxHRxzrBSmKlMj/RmCJZBqv2Jb+zn0zMpW5eM" + + "EqKQ6WDgxSVH23fUHuz8dMNMDPL0ZPtEirGTfgVEFdCov9FDmGgErZYefVzPiI8/" + + "7X/HRtcCgYEApQ2YS+0DLPqaM0cC6/6hDr/jmHLFhHaV6DZR7M9HHDnMN2uMlOEa" + + "RYvXRMBjyQ7LQkwOj6K5k8MVrsDDM5dbekTBgcJMHfM9uViDkB0VPYULORmDJ20N" + + "MLowIAiSon2B2/isatY80YtFq+bRyvPOzjGvinHN3MU1GH/gFuS0fiw=" + "-----END RSA PRIVATE KEY-----"; + RsaRawEncryptor encryptor_2048 = new RsaRawEncryptor(pemData); + assertThat(encryptor_2048.decrypt(encryptor_2048.encrypt("encryptor"))).isEqualTo("encryptor"); + } + + @Test + public void roundTrip4096Key() { + String pemData = "-----BEGIN RSA PRIVATE KEY-----" + + "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" + "-----END RSA PRIVATE KEY-----"; + RsaRawEncryptor encryptor_4096 = new RsaRawEncryptor(pemData); + assertThat(encryptor_4096.decrypt(encryptor_4096.encrypt("encryptor"))).isEqualTo("encryptor"); + } + + private static final String SHORT_STRING = "Bacon ipsum dolor sit amet tail pork loin pork chop filet mignon flank fatback tenderloin boudin shankle corned beef t-bone short ribs. Meatball capicola ball tip short loin beef ribs shoulder, kielbasa pork chop meatloaf biltong porchetta bresaola t-bone spare ribs. Andouille t-bone sausage ground round frankfurter venison. Ground round meatball chicken ribeye doner tongue porchetta."; + + private static String LONG_STRING; + +} diff --git a/crypto/src/test/java/org/springframework/security/crypto/encrypt/RsaSecretEncryptorTests.java b/crypto/src/test/java/org/springframework/security/crypto/encrypt/RsaSecretEncryptorTests.java new file mode 100644 index 00000000000..9fd3b7067ca --- /dev/null +++ b/crypto/src/test/java/org/springframework/security/crypto/encrypt/RsaSecretEncryptorTests.java @@ -0,0 +1,121 @@ +/* + * Copyright 2013-2023 the original author or authors. + * + * Licensed 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 + * + * https://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.springframework.security.crypto.encrypt; + +import java.security.PublicKey; +import java.security.interfaces.RSAPublicKey; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; + +/** + * @author Dave Syer + * + */ +public class RsaSecretEncryptorTests { + + private RsaSecretEncryptor encryptor = new RsaSecretEncryptor(); + + @BeforeEach + public void init() { + LONG_STRING = SHORT_STRING + SHORT_STRING + SHORT_STRING + SHORT_STRING; + for (int i = 0; i < 4; i++) { + LONG_STRING = LONG_STRING + LONG_STRING; + } + } + + @Test + public void roundTripKey() { + PublicKey key = RsaKeyHelper.generateKeyPair().getPublic(); + String encoded = RsaKeyHelper.encodePublicKey((RSAPublicKey) key, "application"); + assertThat(RsaKeyHelper.parsePublicKey(encoded)).isEqualTo(key); + } + + @Test + public void roundTrip() { + assertThat(this.encryptor.decrypt(this.encryptor.encrypt("encryptor"))).isEqualTo("encryptor"); + } + + @Test + public void roundTripWithSalt() { + this.encryptor = new RsaSecretEncryptor(RsaAlgorithm.OAEP, "somesalt"); + assertThat(this.encryptor.decrypt(this.encryptor.encrypt("encryptor"))).isEqualTo("encryptor"); + } + + @Test + public void roundTripWithHexSalt() { + this.encryptor = new RsaSecretEncryptor(RsaAlgorithm.OAEP, "beefea"); + assertThat(this.encryptor.decrypt(this.encryptor.encrypt("encryptor"))).isEqualTo("encryptor"); + } + + @Test + public void roundTripWithLongSalt() { + this.encryptor = new RsaSecretEncryptor(RsaAlgorithm.OAEP, "somesaltsomesaltsomesaltsomesaltsomesalt"); + assertThat(this.encryptor.decrypt(this.encryptor.encrypt("encryptor"))).isEqualTo("encryptor"); + } + + @Test + public void roundTripOaep() { + this.encryptor = new RsaSecretEncryptor(RsaAlgorithm.OAEP); + assertThat(this.encryptor.decrypt(this.encryptor.encrypt("encryptor"))).isEqualTo("encryptor"); + } + + @Test + public void roundTripOaepGcm() { + this.encryptor = new RsaSecretEncryptor(RsaAlgorithm.OAEP, true); + assertThat(this.encryptor.decrypt(this.encryptor.encrypt("encryptor"))).isEqualTo("encryptor"); + } + + @Test + public void roundTripWithMixedAlgorithm() { + RsaSecretEncryptor oaep = new RsaSecretEncryptor(RsaAlgorithm.OAEP); + assertThatIllegalStateException().isThrownBy(() -> oaep.decrypt(this.encryptor.encrypt("encryptor"))); + } + + @Test + public void roundTripWithMixedSalt() { + RsaSecretEncryptor other = new RsaSecretEncryptor(this.encryptor.getPublicKey(), RsaAlgorithm.DEFAULT, "salt"); + assertThatIllegalStateException().isThrownBy(() -> this.encryptor.decrypt(other.encrypt("encryptor"))); + } + + @Test + public void roundTripWithPublicKeyEncryption() { + RsaSecretEncryptor encryptor = new RsaSecretEncryptor(this.encryptor.getPublicKey()); + RsaSecretEncryptor decryptor = this.encryptor; + assertThat(decryptor.decrypt(encryptor.encrypt("encryptor"))).isEqualTo("encryptor"); + } + + @Test + public void publicKeyCannotDecrypt() { + RsaSecretEncryptor encryptor = new RsaSecretEncryptor(this.encryptor.getPublicKey()); + assertThat(encryptor.canDecrypt()).as("Encryptor schould not be able to decrypt").isFalse(); + assertThatIllegalStateException().isThrownBy(() -> encryptor.decrypt(encryptor.encrypt("encryptor"))); + } + + @Test + public void roundTripLongString() { + assertThat(this.encryptor.decrypt(this.encryptor.encrypt(LONG_STRING))).isEqualTo(LONG_STRING); + } + + private static final String SHORT_STRING = "Bacon ipsum dolor sit amet tail pork loin pork chop filet mignon flank fatback tenderloin boudin shankle corned beef t-bone short ribs. Meatball capicola ball tip short loin beef ribs shoulder, kielbasa pork chop meatloaf biltong porchetta bresaola t-bone spare ribs. Andouille t-bone sausage ground round frankfurter venison. Ground round meatball chicken ribeye doner tongue porchetta."; + + private static String LONG_STRING; + +} diff --git a/crypto/src/test/resources/bad.pem b/crypto/src/test/resources/bad.pem new file mode 100644 index 00000000000..653a6eaea7d --- /dev/null +++ b/crypto/src/test/resources/bad.pem @@ -0,0 +1,2 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAwClFgrRa/PUHPIJr9gvIPL6g6Rjp/TVZmVNOf2fL96DYbkj5 \ No newline at end of file diff --git a/crypto/src/test/resources/fake.pem b/crypto/src/test/resources/fake.pem new file mode 100644 index 00000000000..931e57889d8 --- /dev/null +++ b/crypto/src/test/resources/fake.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICWwIBAAKBgQDMWnfaQ0yLFXelprq2S8UurnaGvxFNUdbmTyJeycem5vGLycEY +T4KcdVCTU5491cjbk5GcHjoj2efRSO0y0aXIlUJpLofDdML/SuGLZWp/GbEv978M +pZIztK8iaIm7D/D7by8aws1RJyD9T+lZDAGY7eFfMp0EQyHOcEL0NGFLuwIDAQAB +AoGAWwC6uO8ZaiKwOouqQD4z3FsDG3SA/v7ABaYd9zpCd9gGnyrEm8/kqUoxDLrD +EGRg4y+vO2fWmlqSuoeQYf4spf+vi2di+mGIb6nGe7TpMLPa7lFLOSQHZRx5M5H6 +JDhfhAHlKmF9gLGvDHbpyErzn5YXjcu0PoFiNC1y445D8iECQQDvJzkGbJ9l9vb0 +oRyGXRDpddUcVMECLLB9NKmTl/zKy/qVPD+zYNoi87ePBJFbgmAXRjhhTk2uSBRP +NtVaMoXLAkEA2r+ugzjsLZQIYz/9gxdzdbKWDgpSPbhKCR4bOmrDgJMcOVjtwW+n ++liaX6zwI0QEgCAWLzCbbYDmj3kJrRwT0QJAaowg/dm7EmR7FfYJjVs9Q6X5skuY +Se27G60wt88JExjZpU9YWgSWaugGKbOxRwHI6dWhHMkUFseKNNiLKUpFDQJALIGP +ahdsxiE2S6s7Uy60SSAas6SZ8wDJ320GsS4DtOc5eNmFFjQ3gxH/5rNy8FnoaIEe +wl8rYG43er1voI7z4QJAB4qaqBo7eeiRgnUVIccaSZkNIMSrZ9QUjVFRgfLwAXDO +Ae+t6V+eB0oaIXczA+BLj3Oe6D3iHRGHrxGlcvDdHw== +-----END RSA PRIVATE KEY----- diff --git a/crypto/src/test/resources/keystore.jks b/crypto/src/test/resources/keystore.jks new file mode 100644 index 0000000000000000000000000000000000000000..c13b189d0b81ec03ca47505736006008474929b4 GIT binary patch literal 3174 zcmeH}XHXN^8irGm7J5@aFd`rb#ssA|g-`?p0qH0UNHq}31w=`VfPg3{O^uNjV39#X z2`yAXT#AAq9SjhfODNK7SoY4ndw0f}`|sXg{x~z|dw!jH=X=jQtOeEr0002}b$CNO zgG1asgF<|6`nbD=cme=GOr=-pB>>c3`8+}$002iK2;eXT0dzeJ3<82c5Dfqcf`GAc zD8w4aZo+^-E+_!ue~u5q!v?m6un8Wq#rR_3nrKM>9z2-gn;{1-tS7loZu zL8zh-NQ4R!iE{jNJcmH4{cHR`zlH#W|2+;M6aXdwc>xFlkP}1z0s$|5ahIOEbsxxS z)lsvW6)Qdbp6Kd1JsWoi(M?R+Y`f6EPlaJathnVr6nd&VB*N>OLRa-PIDBylmB~Vu z=DF+=)BnKtT>92|`38l@p3=7)fBH*2C8Rg*p82|V4Cx8xgBI4el%&o7+gCwd8+%*1 zG=){ZlzS=t^=<5W^=2bfzG|W>n6CqwTe58T+kJxYm6pwTrTUg3OKiBt8#Qi;Ja$ci zev5;Wjf;aA1M9M~9&hyOG-|FTS})QYuACp-Q8OU{A0sW4bfk-WWLLb0R(-(>4T&+U zxiXg~S}1RZ%8ZR$qfOpQir*qgcf>^q^aXN6+Z#TZ(wE99AKwOnfdF9c34|0v@^C{W zc_1PXA(Q*tDGb+P)y^Q+VuBdeT7SLt!{0+Z+^NG5fqZpaIX_#{53Z+wR}1p**{R`l><6c36#Bv3Gd0693U z8>b>05CT*H@dFQc4nly;Gq-w+`;zWfx1#%VqhW5ntAcy8H1E93f~NNo+L7Jivlgf7 z7pcws3odpZ1HrPPVzA(k=SR9^v8U|L%0VTrz~PNO#WrHaFXNO}mlSZfqv2+T_Qxcm zYZ+*=%Y-z8j@p?<6Zu%8EwniE|*bN!|G6$d3@*}B!`m3wZoKu4Pw zTOpw}8-its4EkNpJ*fr76uDS4Cqur@%AD`w<)0Dc_R0aH@sNYY0cJq@+{u=^X9KHG zJFr>P$9SCw;KU^_1!2gy0)%?cUT?>PPhrv-*JW$?oy&DjIv2 zg>(KFRZ^LjQ7X)wlW)44=gbsolk%cmXW&5(wrW=9=;J3!3|#{jhDaKYJ5i&rUu_=4 zMD5UY)=h^d@b5=ozuQfB#?ku58r?BjTpVqcKq*Pkl6PN2AYH^DmF9 zQMu*0W$J*h^m{*=aC=@DF+W|@VcKYGPVs3df-G{;Xk{}GqC}P8G4RfQYe(=q*(F7X zq|&hn^QLQ?ahqalNbSWADIJr_!K7WD${@qInHgl_hkoVI_-nV#z0Z?N*JTZ3>)d-G zx3l*&s)I8EzO=&d8mAYuME7Ci@N1DG0+HGbI$&x6^x87=pe8UHm)p9?br(va(R61>TfnDr7M*#tTSowAqqxa$gRk(JA(NxZE24cU#GQiP zW!78ZsIymcC$x^kh{;g(Vf8L(1&6_lF zy!E=R(a_XEI#sg;=E53b$eW+pUJBNCL_aT;Gi5y;?pT{>dF><_bXB|z)8KEc!;&^A z-~ZG&I+v=PJQUioXIyGQWfSsyHRD^XP*%xf_-WBvQ}30&rj*9U=lZRkSN1Oxq2QbdM)pEi(Z)R?M||+;P5ZnNx|mF2ewc4A!J59*(C|Ws3u(274KuB9w=mB zK-HlP25cMDNFAQ%9p-J8FbWO2q+;hzCl`LjZ0{zsJqt2Cib@@HS5Hmv+ZF`U}6HQe6 zf+-g8D>6uEB<0KHI*?^!a%-##^H`71e)koa<3=^xG#oQGI}^IHzXY-of4gGeov&<` zZfQ8IjM8|Y+!5X>on^9COWT{>KZl9!Y_Jr;;RcY(b(yaPOF+M-6=(der^$OxSI+P8 zBL@8}r@hKzzn`Yv!~)NtG_gG=e_z6w@A;0=s90jvcT~yAOf~1Jx>>)E4bEIc6*vkW zn{g-Wb6@x5w)EP}#SX5;W8z_5xBvMu{#2CzXx4ufrk{%PQ&Ij`ih^wQNFF!7lu^W- ObuAizJiMbxI`{*#yig(l literal 0 HcmV?d00001 diff --git a/crypto/src/test/resources/keystore.pkcs12 b/crypto/src/test/resources/keystore.pkcs12 new file mode 100644 index 0000000000000000000000000000000000000000..a6d7c0e7598561660aa4d9592e1889be52c06029 GIT binary patch literal 3589 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 literal 0 HcmV?d00001 diff --git a/crypto/src/test/resources/spacey.pem b/crypto/src/test/resources/spacey.pem new file mode 100644 index 00000000000..1050b1dc1aa --- /dev/null +++ b/crypto/src/test/resources/spacey.pem @@ -0,0 +1,25 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAwClFgrRa/PUHPIJr9gvIPL6g6Rjp/TVZmVNOf2fL96DYbkj5 +4YbrwfKwjoTjk1M6gLQpOA4Blocx6zN5OnICnVGlVM9xymWxTxxCfc2tE2Fai9I1wchULCChhwm/UU5ZNi3KpXinlyamSYw+lMQkZ8gTXCgOEvs2j9E1quF4pvy1BZKvbD8tUnUQlyiKRnI6gOxQL8B6OAYPRdaa9FVNmrs1B4eDPG918L2f1pT090P1n+tw +iejNgQvtSD78/A88qt89OhzscsufALTrBjycn89kkfBd0zbVLF0W6+ZVLZrf97/y +LCoGSCcZL9LFPNvNqxOnleviDco7aOs4stQ9jQIDAQABAoIBAQC1TbthyN0YUe+T + 7dIDAbbZaVrU00biOtXgzjMADmTprP7Hf18UpIIIKfzfWw6FUD+gc1t4oe5pogE9 +UwGMXUmOORxu2pMYTb5vT9CEdexYnsAZsCo8PdD9GYSNrmquQef2MFpEqYQmHrdC + KWpaXn2i1ak+iCRPUGp4YwHpynZVxfE8z/AIsPn6NPDh6SnCXb1rTgQe2UCfXm93 +UJe5F/OR2kQi5KFO+dxLmCOBCwr6SGCLH+VotGpuxCVRUd9sJ/d4QpDZEgjuf7Ug +eQHfgMDS/tc09B9rl0dwKnEa31kcQ9X9KLkKP+w0Pqhh0Emny20eg9jS6XNayg61 +p/LQtW9BAoGBAO5veKMIcXfZmuh11WIIhdhLKkNtYyt5NDmrV8/IVScLFvjB0ftt +8PAtXo/ekOHkyITyIumQ9l4VCvacNw7DyV9FYk4WvrvVYOCL8aZi+O5+12NT67eO +Rr/voGlRoV05X7+inc90qbbYJ8lRmLSqvzmsm98mkuhw/FKGRhVZIfAJAoGBAM5R + I5vK6cJxOwXQOEGOd5/8B9JMFXyuendXo/N2/NxSQsbx4pc3v2rv/eGJYaY7Nx/y +2M/vdWYkpG59PAS3k2TrCA/0SGmyVqY+c8BomKisU5VaBlIPfGuec9tDPgWCp8Ur +3Jjt/2sVoa0vMkqymUqMb9HyH9tdI9oyh7EOOrplAoGAR6DlNNUMgVy11K/Rcqns +y5WJFMh/ykeXENwQfTNJoXkLZZ+UXVwhzYVTqxTJoZMBSi8TnecWnBzmNj+nqp/W + lvBZH+xlUDhB6jMgXUPOVJd2TTigz3vGdVKfdgQ33bGmugM4NWJuuacmDKyem2fQ + GptoGBmWeI24v3HnC/LC50ECgYAz0iN8hRnz0db+Xc9TgAJB997LDnszJuvxv9yZ + UWCvwiWtrKG6U7FLnd4J4STayPLOnoOgrsexETEP43rIwIdQCMysnTH3AmlLNlKC + mIMHksknsUX3JJaevVziTOBuJ+QV3S96ZgUKk5NZWYprQrLIC8AmXodr5NgVfS2h + 5i4QFQKBgFfbYHiMw5AAUQrBNkrAjLd1wIaO/6qS3w4OsCWKowhfaJLEXAbIRV7s +vAtgtlCovdasVj4RRLXFf+73naVTQjBZI+3jWHHyFk3+Zy86mQCSGv9WuDVV1IhS +h8InTVvK8wgdgX7qiw3pvU0roqNW4/j4j8OqJO3Zt4KO2iX8htsO +-----END RSA PRIVATE KEY-----