Alon Bar-Lev has uploaded a new change for review. Change subject: uutil: crypto: EnvelopeEncryptDecrypt initial implementation ......................................................................
uutil: crypto: EnvelopeEncryptDecrypt initial implementation Change-Id: I3b7444ba279bbfaadd162dee4a552b434249aaac Signed-off-by: Alon Bar-Lev <alo...@redhat.com> --- A backend/manager/modules/uutils/src/main/java/org/ovirt/engine/core/uutils/crypto/EnvelopeEncryptDecrypt.java A backend/manager/modules/uutils/src/test/java/org/ovirt/engine/core/uutils/crypto/EnvelopeEncryptDecryptTest.java A backend/manager/modules/uutils/src/test/resources/key2.p12 3 files changed, 226 insertions(+), 0 deletions(-) git pull ssh://gerrit.ovirt.org:29418/ovirt-engine refs/changes/30/42330/1 diff --git a/backend/manager/modules/uutils/src/main/java/org/ovirt/engine/core/uutils/crypto/EnvelopeEncryptDecrypt.java b/backend/manager/modules/uutils/src/main/java/org/ovirt/engine/core/uutils/crypto/EnvelopeEncryptDecrypt.java new file mode 100644 index 0000000..a0df435 --- /dev/null +++ b/backend/manager/modules/uutils/src/main/java/org/ovirt/engine/core/uutils/crypto/EnvelopeEncryptDecrypt.java @@ -0,0 +1,141 @@ +package org.ovirt.engine.core.uutils.crypto; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.security.GeneralSecurityException; +import java.security.Key; +import java.security.KeyException; +import java.security.KeyStore; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.cert.Certificate; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.spec.IvParameterSpec; + +import org.apache.commons.codec.binary.Base64; +import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.map.type.TypeFactory; + +public class EnvelopeEncryptDecrypt { + + private static final String PUBKEY_DIGEST_ALGO = "SHA-1"; + private static final String PKEY_MODE_PADDING = "ECB/PKCS1Padding"; + + private static final String CONTENT_KEY = "content"; + private static final String RANDOM_KEY = "random"; + + private static final String CIPHER_ALGO_KEY = "cipherAlgo"; + private static final String ENCRYPTED_CONTENT_KEY = "encryptedContent"; + private static final String IV_KEY = "iv"; + private static final String WRAPPED_KEY_KEY = "wrappedKey"; + private static final String WRAP_ALGO_KEY = "wrapAlgo"; + private static final String WRAP_KEY_DIGEST_ALGO_KEY = "wrapKeyDigestAlgo"; + private static final String WRAP_KEY_DIGEST_KEY = "wrapKeyDigest"; + + private static final Random random; + static { + try { + random= SecureRandom.getInstance("SHA1PRNG"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + /** + * Encrypt a content using envelope. + * @param algo Cipher algorithm to use. + * @param bits Size of cipher key. + * @param cert Certificate to encrypt to (wrap key using public key). + * @param blockSize Adjust the size of content to blockSize. + * @param content Content to encrypt. + * @return Base64 value of envelope. + * + * The blockSize is used in order to hide actual content size. + */ + public static String Encrypt( + String algo, + int bits, + Certificate cert, + int blockSize, + byte[] content + ) throws GeneralSecurityException, IOException { + final String wrapAlgo = cert.getPublicKey().getAlgorithm() + "/" + PKEY_MODE_PADDING; + final Base64 base64 = new Base64(0); + final Map<String, String> map = new HashMap<String, String>(); + final Map<String, String> env = new HashMap<String, String>(); + + env.put(CONTENT_KEY, base64.encodeToString(content)); + byte[] r = new byte[((content.length / blockSize) + 1) * blockSize - content.length]; + random.nextBytes(r); + env.put(RANDOM_KEY, base64.encodeToString(r)); + + KeyGenerator gen = KeyGenerator.getInstance(algo.split("/", 2)[0]); + gen.init(bits); + Key key = gen.generateKey(); + Cipher cipher = Cipher.getInstance(algo); + cipher.init(Cipher.ENCRYPT_MODE, key); + Cipher wrap = Cipher.getInstance(wrapAlgo); + wrap.init(Cipher.WRAP_MODE, cert); + + map.put(WRAP_ALGO_KEY, wrapAlgo); + map.put(CIPHER_ALGO_KEY, algo); + map.put(ENCRYPTED_CONTENT_KEY, base64.encodeToString(cipher.doFinal(new ObjectMapper().writeValueAsString(env).getBytes(Charset.forName("UTF-8"))))); + map.put(IV_KEY, base64.encodeToString(cipher.getIV())); + map.put(WRAPPED_KEY_KEY, base64.encodeToString(wrap.wrap(key))); + map.put(WRAP_KEY_DIGEST_ALGO_KEY, PUBKEY_DIGEST_ALGO); + map.put(WRAP_KEY_DIGEST_KEY, base64.encodeToString(MessageDigest.getInstance(PUBKEY_DIGEST_ALGO).digest(cert.getPublicKey().getEncoded()))); + return base64.encodeToString(new ObjectMapper().writeValueAsString(map).getBytes(Charset.forName("UTF-8"))); + } + + /** + * Decrypt a content using envelope. + * @param pkeyEntry A private key entry (key and certificate) to use for decryption. + * @param blob value of envelope. + * @return content. + */ + public static byte[] Decrypt( + KeyStore.PrivateKeyEntry pkeyEntry, + String blob + ) throws GeneralSecurityException, IOException { + final Map<String, String> map = new ObjectMapper().readValue( + Base64.decodeBase64(blob), + TypeFactory.defaultInstance().constructMapType(HashMap.class, String.class, String.class) + ); + + if (pkeyEntry.getCertificate() != null) { + if ( + !MessageDigest.isEqual( + Base64.decodeBase64(map.get(WRAP_KEY_DIGEST_KEY)), + MessageDigest.getInstance(map.get(WRAP_KEY_DIGEST_ALGO_KEY)).digest(pkeyEntry.getCertificate().getPublicKey().getEncoded()) + ) + ) { + throw new KeyException("Private key entry mismatch"); + } + } + + Cipher wrap = Cipher.getInstance(map.get(WRAP_ALGO_KEY)); + wrap.init(Cipher.UNWRAP_MODE, pkeyEntry.getPrivateKey()); + Cipher cipher = Cipher.getInstance(map.get(CIPHER_ALGO_KEY)); + cipher.init( + Cipher.DECRYPT_MODE, + wrap.unwrap( + Base64.decodeBase64(map.get(WRAPPED_KEY_KEY)), + cipher.getAlgorithm().split("/", 2)[0], + Cipher.SECRET_KEY + ), + new IvParameterSpec(Base64.decodeBase64(map.get(IV_KEY))) + ); + + final Map<String, String> env = new ObjectMapper().readValue( + cipher.doFinal(Base64.decodeBase64(map.get(ENCRYPTED_CONTENT_KEY))), + TypeFactory.defaultInstance().constructMapType(HashMap.class, String.class, String.class) + ); + return Base64.decodeBase64(env.get(CONTENT_KEY)); + } + +} diff --git a/backend/manager/modules/uutils/src/test/java/org/ovirt/engine/core/uutils/crypto/EnvelopeEncryptDecryptTest.java b/backend/manager/modules/uutils/src/test/java/org/ovirt/engine/core/uutils/crypto/EnvelopeEncryptDecryptTest.java new file mode 100644 index 0000000..e65a5d9 --- /dev/null +++ b/backend/manager/modules/uutils/src/test/java/org/ovirt/engine/core/uutils/crypto/EnvelopeEncryptDecryptTest.java @@ -0,0 +1,85 @@ +package org.ovirt.engine.core.uutils.crypto; + +import static org.junit.Assert.assertArrayEquals; + +import java.io.InputStream; +import java.nio.charset.Charset; +import java.security.KeyException; +import java.security.KeyStore; + +import org.junit.Test; + +public class EnvelopeEncryptDecryptTest { + + private static KeyStore getKeyStore(String storeType, String store, String password) throws Exception { + KeyStore ks = KeyStore.getInstance(storeType); + try (InputStream is = ClassLoader.getSystemResourceAsStream(store)) { + ks.load(is, password.toCharArray()); + } + return ks; + } + + private static KeyStore.PrivateKeyEntry getPrivateKeyEntry(KeyStore ks, String alias, String password) throws Exception { + return (KeyStore.PrivateKeyEntry)ks.getEntry(alias, new KeyStore.PasswordProtection(password.toCharArray())); + } + + @Test + public void test1() throws Exception { + KeyStore.PrivateKeyEntry entry = getPrivateKeyEntry(getKeyStore("PKCS12", "key.p12", "NoSoup4U"), "1", "NoSoup4U"); + + byte[] test = "testing 1 2 3 4".getBytes(Charset.forName("UTF-8")); + + assertArrayEquals( + test, + EnvelopeEncryptDecrypt.Decrypt( + entry, + EnvelopeEncryptDecrypt.Encrypt( + "AES/OFB/PKCS5Padding", + 256, + entry.getCertificate(), + 100, + test + ) + ) + ); + } + + @Test + public void test2() throws Exception { + KeyStore.PrivateKeyEntry entry = getPrivateKeyEntry(getKeyStore("PKCS12", "key.p12", "NoSoup4U"), "1", "NoSoup4U"); + + byte[] test = "testing 1 2 3 4".getBytes(Charset.forName("UTF-8")); + String blob = "eyJjaXBoZXJBbGdvIjoiQUVTL09GQi9QS0NTNVBhZGRpbmciLCJpdiI6Im01aDgxYUxmdkNycXZtS3R3L290SFE9PSIsIndyYXBLZXlEaWdlc3QiOiJXNkdCbHFrRUlwbUx2aE94VFlybHpwV1JrQjg9Iiwid3JhcHBlZEtleSI6ImE1TXhCWFFBY25kZi95U2J4YWRMdWwySWxHNFpkTWgzOHlUdE9HLzI2MDBHR204ZHRudklpSSsxakE3c0k4cDI1VVAwbWFBL29KdG9xTUVHd0Fhc3VNdkxhVEFPbHJId1RwM2ROaWlWSUFlNmREY21WVTYrd0JLNEljMjBNdjluKzQrRVExUTZ6OXBWSldXQjEzNXk4TEM3NlUzK2czVnBkVm5SWFNGTllpUjR3L2xGMFllVVFxR1J1RnlVRzJLUEV1RzFZTjl5amZjeDB1RWxKb1VYRGNoUDlOY3VqaE9pUHcvTUJ0YzRzSjNjTVZobFh1ZXFtTG85ZE1KWngzS2wzWkNFQTlrMS9Md2wvRzZwNXlsc2gxeWwxSmhwL2M3RmRDRjRJcWt6cFZVejZsZXd4cktnZFhlSG1oOE1CWk5BdkJIK2plYmZrRitlcHZ0c0t0SWJBZz09IiwiZW5jcnlwdGVkQ29udGVudCI6IlhJajluaTFQL1VVZUptOUhYc0orYWIvSkxGS1p1ZnRlM0c1TkFIME0xbkFUR3VtMEwweEhNKzhSYUY0aHZ6dFJ1S3FZb0cxYytmR3dVTG9rRW1pejczZzRmQzZhUTBWL3BManNray81eW43OEZQYVpOQU9oM1ByNmFOemNGbkR0NVBnOVJiK3h5TzIzM0ZlTGw4bWMvOFVRSUEzUURQT1pvbmlxWVdEOXM5VEhYbG56Z1dUSE5GY2g3U0VSZHZqOUtRdHcvUkxPMS9sTVMybmtONUJUZHozYkx2ZXY5YjNWMWdrY! 1ZKRDRpams9Iiwid3JhcEtleURpZ2VzdEFsZ28iOiJTSEEtMSIsIndyYXBBbGdvIjoiUlNBL0VDQi9QS0NTMVBhZGRpbmcifQ=="; + + assertArrayEquals( + test, + EnvelopeEncryptDecrypt.Decrypt( + entry, + blob + ) + ); + } + + @Test(expected=KeyException.class) + public void testInvalidKey() throws Exception { + KeyStore.PrivateKeyEntry entry1 = getPrivateKeyEntry(getKeyStore("PKCS12", "key.p12", "NoSoup4U"), "1", "NoSoup4U"); + KeyStore.PrivateKeyEntry entry2 = getPrivateKeyEntry(getKeyStore("PKCS12", "key2.p12", "mypass"), "1", "mypass"); + + byte[] test = "testing 1 2 3 4".getBytes(Charset.forName("UTF-8")); + + assertArrayEquals( + test, + EnvelopeEncryptDecrypt.Decrypt( + entry2, + EnvelopeEncryptDecrypt.Encrypt( + "AES/OFB/PKCS5Padding", + 256, + entry1.getCertificate(), + 100, + test + ) + ) + ); + } + +} diff --git a/backend/manager/modules/uutils/src/test/resources/key2.p12 b/backend/manager/modules/uutils/src/test/resources/key2.p12 new file mode 100644 index 0000000..14db512 --- /dev/null +++ b/backend/manager/modules/uutils/src/test/resources/key2.p12 Binary files differ -- To view, visit https://gerrit.ovirt.org/42330 To unsubscribe, visit https://gerrit.ovirt.org/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I3b7444ba279bbfaadd162dee4a552b434249aaac Gerrit-PatchSet: 1 Gerrit-Project: ovirt-engine Gerrit-Branch: master Gerrit-Owner: Alon Bar-Lev <alo...@redhat.com> _______________________________________________ Engine-patches mailing list Engine-patches@ovirt.org http://lists.ovirt.org/mailman/listinfo/engine-patches