mirror of
https://codeberg.org/Mercury-IM/Smack
synced 2024-11-22 06:12:05 +01:00
[omemo] Introduce OmemoAesCipher as central AES API
OmemoAesCipher is the sole point where OMEMO related AES operations are performed. This allows OmemoAesCipher to check in a static block if AES is available. If AES is not available it throws a (hopefully) helpfull exception message. Typically AES is not available on Android if no security provider providing AES, like Bouncy Castle, has been explicitly configured.
This commit is contained in:
parent
b7905d585d
commit
14142a0ef2
5 changed files with 120 additions and 51 deletions
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2017 Paul Schaub
|
* Copyright 2017 Paul Schaub, 2021 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -17,7 +17,9 @@
|
||||||
package org.jivesoftware.smackx.omemo;
|
package org.jivesoftware.smackx.omemo;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
@ -25,6 +27,7 @@ import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.crypto.BadPaddingException;
|
import javax.crypto.BadPaddingException;
|
||||||
import javax.crypto.IllegalBlockSizeException;
|
import javax.crypto.IllegalBlockSizeException;
|
||||||
|
import javax.crypto.NoSuchPaddingException;
|
||||||
|
|
||||||
import org.jivesoftware.smackx.omemo.element.OmemoElement;
|
import org.jivesoftware.smackx.omemo.element.OmemoElement;
|
||||||
import org.jivesoftware.smackx.omemo.element.OmemoKeyElement;
|
import org.jivesoftware.smackx.omemo.element.OmemoKeyElement;
|
||||||
|
@ -172,11 +175,10 @@ public abstract class OmemoRatchet<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
|
||||||
byte[] encryptedBody = payloadAndAuthTag(element, cipherAndAuthTag.getAuthTag());
|
byte[] encryptedBody = payloadAndAuthTag(element, cipherAndAuthTag.getAuthTag());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String plaintext = new String(cipherAndAuthTag.getCipher().doFinal(encryptedBody), StandardCharsets.UTF_8);
|
return cipherAndAuthTag.decrypt(encryptedBody);
|
||||||
return plaintext;
|
} catch (IllegalBlockSizeException | BadPaddingException | InvalidKeyException | NoSuchAlgorithmException
|
||||||
} catch (IllegalBlockSizeException | BadPaddingException e) {
|
| NoSuchPaddingException | InvalidAlgorithmParameterException e) {
|
||||||
throw new CryptoFailedException("decryptMessageElement could not decipher message body: "
|
throw new CryptoFailedException("decryptMessageElement could not decipher message body", e);
|
||||||
+ e.getMessage());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2017 Paul Schaub, 2019 Florian Schmaus
|
* Copyright 2017 Paul Schaub, 2019-2021 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,18 +16,14 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.omemo.internal;
|
package org.jivesoftware.smackx.omemo.internal;
|
||||||
|
|
||||||
import static org.jivesoftware.smackx.omemo.util.OmemoConstants.Crypto.CIPHERMODE;
|
import java.nio.charset.StandardCharsets;
|
||||||
import static org.jivesoftware.smackx.omemo.util.OmemoConstants.Crypto.KEYTYPE;
|
|
||||||
|
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.BadPaddingException;
|
||||||
|
import javax.crypto.IllegalBlockSizeException;
|
||||||
import javax.crypto.NoSuchPaddingException;
|
import javax.crypto.NoSuchPaddingException;
|
||||||
import javax.crypto.spec.IvParameterSpec;
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
|
||||||
|
|
||||||
import org.jivesoftware.smackx.omemo.exceptions.CryptoFailedException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encapsulate Cipher and AuthTag.
|
* Encapsulate Cipher and AuthTag.
|
||||||
|
@ -45,21 +41,10 @@ public class CipherAndAuthTag {
|
||||||
this.wasPreKey = wasPreKey;
|
this.wasPreKey = wasPreKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Cipher getCipher() throws CryptoFailedException {
|
public String decrypt(byte[] ciphertext) throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException,
|
||||||
|
NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException {
|
||||||
Cipher cipher;
|
byte[] plaintext = OmemoAesCipher.decryptAesGcmNoPadding(ciphertext, key, iv);
|
||||||
try {
|
return new String(plaintext, StandardCharsets.UTF_8);
|
||||||
cipher = Cipher.getInstance(CIPHERMODE);
|
|
||||||
SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE);
|
|
||||||
IvParameterSpec ivSpec = new IvParameterSpec(iv);
|
|
||||||
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
|
|
||||||
} catch (NoSuchAlgorithmException | java.security.InvalidKeyException |
|
|
||||||
InvalidAlgorithmParameterException |
|
|
||||||
NoSuchPaddingException e) {
|
|
||||||
throw new CryptoFailedException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return cipher;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getAuthTag() {
|
public byte[] getAuthTag() {
|
||||||
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Copyright 2017 Paul Schaub, 2019-2021 Florian Schmaus
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.jivesoftware.smackx.omemo.internal;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
import javax.crypto.BadPaddingException;
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.IllegalBlockSizeException;
|
||||||
|
import javax.crypto.NoSuchPaddingException;
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.util.RandomUtil;
|
||||||
|
import org.jivesoftware.smackx.omemo.util.OmemoConstants;
|
||||||
|
import org.jivesoftware.smackx.omemo.util.OmemoMessageBuilder;
|
||||||
|
|
||||||
|
public class OmemoAesCipher {
|
||||||
|
|
||||||
|
static {
|
||||||
|
byte[] iv = OmemoMessageBuilder.generateIv();
|
||||||
|
byte[] key = new byte[16];
|
||||||
|
RandomUtil.fillWithSecureRandom(key);
|
||||||
|
|
||||||
|
try {
|
||||||
|
encryptAesGcmNoPadding("This is just a test", key, iv);
|
||||||
|
} catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException
|
||||||
|
| InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
|
||||||
|
String message = "Unable to perform " + OmemoConstants.Crypto.CIPHERMODE
|
||||||
|
+ " operation requires by OMEMO. Ensure that a suitable crypto provider for is available."
|
||||||
|
+ " For example Bouncycastle on Android (BouncyCastleProvider)";
|
||||||
|
throw new AssertionError(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum CipherOpmode {
|
||||||
|
encrypt(Cipher.ENCRYPT_MODE),
|
||||||
|
decrypt(Cipher.DECRYPT_MODE),
|
||||||
|
;
|
||||||
|
|
||||||
|
public final int opmodeInt;
|
||||||
|
|
||||||
|
CipherOpmode(int opmodeInt) {
|
||||||
|
this.opmodeInt = opmodeInt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] performCipherOperation(CipherOpmode opmode, byte[] input, byte[] key,
|
||||||
|
byte[] initializationVector)
|
||||||
|
throws IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException,
|
||||||
|
NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
|
||||||
|
SecretKey secretKey = new SecretKeySpec(key, OmemoConstants.Crypto.KEYTYPE);
|
||||||
|
IvParameterSpec ivSpec = new IvParameterSpec(initializationVector);
|
||||||
|
|
||||||
|
Cipher cipher = Cipher.getInstance(OmemoConstants.Crypto.CIPHERMODE);
|
||||||
|
cipher.init(opmode.opmodeInt, secretKey, ivSpec);
|
||||||
|
|
||||||
|
byte[] ciphertext = cipher.doFinal(input);
|
||||||
|
|
||||||
|
return ciphertext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] decryptAesGcmNoPadding(byte[] ciphertext, byte[] key, byte[] initializationVector)
|
||||||
|
throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException,
|
||||||
|
NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException {
|
||||||
|
return performCipherOperation(CipherOpmode.decrypt, ciphertext, key, initializationVector);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] encryptAesGcmNoPadding(byte[] plaintext, byte[] key, byte[] initializationVector)
|
||||||
|
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
|
||||||
|
InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
|
||||||
|
return performCipherOperation(CipherOpmode.encrypt, plaintext, key, initializationVector);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] encryptAesGcmNoPadding(String plaintext, byte[] key, byte[] initializationVector)
|
||||||
|
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
|
||||||
|
InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
|
||||||
|
byte[] plaintextBytes = plaintext.getBytes(StandardCharsets.UTF_8);
|
||||||
|
return encryptAesGcmNoPadding(plaintextBytes, key, initializationVector);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2017 Paul Schaub, 2019 Florian Schmaus
|
* Copyright 2017 Paul Schaub, 2019-2021 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,25 +16,18 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.omemo.util;
|
package org.jivesoftware.smackx.omemo.util;
|
||||||
|
|
||||||
import static org.jivesoftware.smackx.omemo.util.OmemoConstants.Crypto.CIPHERMODE;
|
|
||||||
import static org.jivesoftware.smackx.omemo.util.OmemoConstants.Crypto.KEYLENGTH;
|
import static org.jivesoftware.smackx.omemo.util.OmemoConstants.Crypto.KEYLENGTH;
|
||||||
import static org.jivesoftware.smackx.omemo.util.OmemoConstants.Crypto.KEYTYPE;
|
import static org.jivesoftware.smackx.omemo.util.OmemoConstants.Crypto.KEYTYPE;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import javax.crypto.BadPaddingException;
|
import javax.crypto.BadPaddingException;
|
||||||
import javax.crypto.Cipher;
|
|
||||||
import javax.crypto.IllegalBlockSizeException;
|
import javax.crypto.IllegalBlockSizeException;
|
||||||
import javax.crypto.KeyGenerator;
|
import javax.crypto.KeyGenerator;
|
||||||
import javax.crypto.NoSuchPaddingException;
|
import javax.crypto.NoSuchPaddingException;
|
||||||
import javax.crypto.SecretKey;
|
|
||||||
import javax.crypto.spec.IvParameterSpec;
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
|
||||||
|
|
||||||
import org.jivesoftware.smack.util.RandomUtil;
|
import org.jivesoftware.smack.util.RandomUtil;
|
||||||
import org.jivesoftware.smackx.omemo.OmemoRatchet;
|
import org.jivesoftware.smackx.omemo.OmemoRatchet;
|
||||||
|
@ -48,6 +41,7 @@ import org.jivesoftware.smackx.omemo.exceptions.NoIdentityKeyException;
|
||||||
import org.jivesoftware.smackx.omemo.exceptions.UndecidedOmemoIdentityException;
|
import org.jivesoftware.smackx.omemo.exceptions.UndecidedOmemoIdentityException;
|
||||||
import org.jivesoftware.smackx.omemo.exceptions.UntrustedOmemoIdentityException;
|
import org.jivesoftware.smackx.omemo.exceptions.UntrustedOmemoIdentityException;
|
||||||
import org.jivesoftware.smackx.omemo.internal.CiphertextTuple;
|
import org.jivesoftware.smackx.omemo.internal.CiphertextTuple;
|
||||||
|
import org.jivesoftware.smackx.omemo.internal.OmemoAesCipher;
|
||||||
import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
|
import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
|
||||||
import org.jivesoftware.smackx.omemo.trust.OmemoFingerprint;
|
import org.jivesoftware.smackx.omemo.trust.OmemoFingerprint;
|
||||||
import org.jivesoftware.smackx.omemo.trust.OmemoTrustCallback;
|
import org.jivesoftware.smackx.omemo.trust.OmemoTrustCallback;
|
||||||
|
@ -160,16 +154,7 @@ public class OmemoMessageBuilder<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encrypt message body
|
// Encrypt message body
|
||||||
SecretKey secretKey = new SecretKeySpec(messageKey, KEYTYPE);
|
byte[] ciphertext = OmemoAesCipher.encryptAesGcmNoPadding(message, messageKey, initializationVector);
|
||||||
IvParameterSpec ivSpec = new IvParameterSpec(initializationVector);
|
|
||||||
Cipher cipher = Cipher.getInstance(CIPHERMODE);
|
|
||||||
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
|
|
||||||
|
|
||||||
byte[] body;
|
|
||||||
byte[] ciphertext;
|
|
||||||
|
|
||||||
body = message.getBytes(StandardCharsets.UTF_8);
|
|
||||||
ciphertext = cipher.doFinal(body);
|
|
||||||
|
|
||||||
byte[] clearKeyWithAuthTag = new byte[messageKey.length + 16];
|
byte[] clearKeyWithAuthTag = new byte[messageKey.length + 16];
|
||||||
byte[] cipherTextWithoutAuthTag = new byte[ciphertext.length - 16];
|
byte[] cipherTextWithoutAuthTag = new byte[ciphertext.length - 16];
|
||||||
|
|
|
@ -22,7 +22,6 @@ import static org.jivesoftware.smackx.omemo.util.OmemoConstants.Crypto.KEYLENGTH
|
||||||
import static org.jivesoftware.smackx.omemo.util.OmemoConstants.Crypto.KEYTYPE;
|
import static org.jivesoftware.smackx.omemo.util.OmemoConstants.Crypto.KEYTYPE;
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertNotNull;
|
|
||||||
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
@ -61,7 +60,6 @@ public class WrapperObjectsTest extends SmackTestSuite {
|
||||||
|
|
||||||
CipherAndAuthTag cat = new CipherAndAuthTag(key, iv, authTag, true);
|
CipherAndAuthTag cat = new CipherAndAuthTag(key, iv, authTag, true);
|
||||||
|
|
||||||
assertNotNull(cat.getCipher());
|
|
||||||
assertArrayEquals(key, cat.getKey());
|
assertArrayEquals(key, cat.getKey());
|
||||||
assertArrayEquals(iv, cat.getIv());
|
assertArrayEquals(iv, cat.getIv());
|
||||||
assertArrayEquals(authTag, cat.getAuthTag());
|
assertArrayEquals(authTag, cat.getAuthTag());
|
||||||
|
|
Loading…
Reference in a new issue