mirror of
https://codeberg.org/Mercury-IM/Smack
synced 2024-06-16 00:24:52 +02:00
14142a0ef2
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.
204 lines
8.8 KiB
Java
204 lines
8.8 KiB
Java
/**
|
|
*
|
|
* Copyright 2017 Paul Schaub, 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;
|
|
|
|
import java.io.IOException;
|
|
import java.security.InvalidAlgorithmParameterException;
|
|
import java.security.InvalidKeyException;
|
|
import java.security.NoSuchAlgorithmException;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.logging.Level;
|
|
import java.util.logging.Logger;
|
|
|
|
import javax.crypto.BadPaddingException;
|
|
import javax.crypto.IllegalBlockSizeException;
|
|
import javax.crypto.NoSuchPaddingException;
|
|
|
|
import org.jivesoftware.smackx.omemo.element.OmemoElement;
|
|
import org.jivesoftware.smackx.omemo.element.OmemoKeyElement;
|
|
import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
|
|
import org.jivesoftware.smackx.omemo.exceptions.CryptoFailedException;
|
|
import org.jivesoftware.smackx.omemo.exceptions.MultipleCryptoFailedException;
|
|
import org.jivesoftware.smackx.omemo.exceptions.NoRawSessionException;
|
|
import org.jivesoftware.smackx.omemo.exceptions.UntrustedOmemoIdentityException;
|
|
import org.jivesoftware.smackx.omemo.internal.CipherAndAuthTag;
|
|
import org.jivesoftware.smackx.omemo.internal.CiphertextTuple;
|
|
import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
|
|
|
|
public abstract class OmemoRatchet<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> {
|
|
private static final Logger LOGGER = Logger.getLogger(OmemoRatchet.class.getName());
|
|
|
|
protected final OmemoManager omemoManager;
|
|
protected final OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> store;
|
|
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* @param omemoManager omemoManager
|
|
* @param store omemoStore
|
|
*/
|
|
public OmemoRatchet(OmemoManager omemoManager,
|
|
OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> store) {
|
|
this.omemoManager = omemoManager;
|
|
this.store = store;
|
|
}
|
|
|
|
/**
|
|
* Decrypt a double-ratchet-encrypted message key.
|
|
*
|
|
* @param sender sender of the message.
|
|
* @param encryptedKey key encrypted with the ratchet of the sender.
|
|
* @return decrypted message key.
|
|
*
|
|
* @throws CorruptedOmemoKeyException if the OMEMO key is corrupted.
|
|
* @throws NoRawSessionException when no double ratchet session was found.
|
|
* @throws CryptoFailedException if the OMEMO cryptography failed.
|
|
* @throws UntrustedOmemoIdentityException if the OMEMO identity is not trusted.
|
|
* @throws IOException if an I/O error occurred.
|
|
*/
|
|
public abstract byte[] doubleRatchetDecrypt(OmemoDevice sender, byte[] encryptedKey)
|
|
throws CorruptedOmemoKeyException, NoRawSessionException, CryptoFailedException,
|
|
UntrustedOmemoIdentityException, IOException;
|
|
|
|
/**
|
|
* Encrypt a messageKey with the double ratchet session of the recipient.
|
|
*
|
|
* @param recipient recipient of the message.
|
|
* @param messageKey key we want to encrypt.
|
|
* @return encrypted message key.
|
|
*/
|
|
public abstract CiphertextTuple doubleRatchetEncrypt(OmemoDevice recipient, byte[] messageKey);
|
|
|
|
/**
|
|
* Try to decrypt the transported message key using the double ratchet session.
|
|
*
|
|
* @param element omemoElement
|
|
* @return tuple of cipher generated from the unpacked message key and the auth-tag
|
|
*
|
|
* @throws CryptoFailedException if decryption using the double ratchet fails
|
|
* @throws NoRawSessionException if we have no session, but the element was NOT a PreKeyMessage
|
|
* @throws IOException if an I/O error occurred.
|
|
*/
|
|
CipherAndAuthTag retrieveMessageKeyAndAuthTag(OmemoDevice sender, OmemoElement element) throws CryptoFailedException,
|
|
NoRawSessionException, IOException {
|
|
int keyId = omemoManager.getDeviceId();
|
|
byte[] unpackedKey = null;
|
|
List<CryptoFailedException> decryptExceptions = new ArrayList<>();
|
|
List<OmemoKeyElement> keys = element.getHeader().getKeys();
|
|
|
|
boolean preKey = false;
|
|
|
|
// Find key with our ID.
|
|
for (OmemoKeyElement k : keys) {
|
|
if (k.getId() == keyId) {
|
|
try {
|
|
unpackedKey = doubleRatchetDecrypt(sender, k.getData());
|
|
preKey = k.isPreKey();
|
|
break;
|
|
} catch (CryptoFailedException e) {
|
|
// There might be multiple keys with our id, but we can only decrypt one.
|
|
// So we can't throw the exception, when decrypting the first duplicate which is not for us.
|
|
decryptExceptions.add(e);
|
|
} catch (CorruptedOmemoKeyException e) {
|
|
decryptExceptions.add(new CryptoFailedException(e));
|
|
} catch (UntrustedOmemoIdentityException e) {
|
|
LOGGER.log(Level.WARNING, "Received message from " + sender + " contained unknown identityKey. Ignore message.", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (unpackedKey == null) {
|
|
if (!decryptExceptions.isEmpty()) {
|
|
throw MultipleCryptoFailedException.from(decryptExceptions);
|
|
}
|
|
|
|
throw new CryptoFailedException("Transported key could not be decrypted, since no suitable message key " +
|
|
"was provided. Provides keys: " + keys);
|
|
}
|
|
|
|
// Split in AES auth-tag and key
|
|
byte[] messageKey = new byte[16];
|
|
byte[] authTag = null;
|
|
|
|
if (unpackedKey.length == 32) {
|
|
authTag = new byte[16];
|
|
// copy key part into messageKey
|
|
System.arraycopy(unpackedKey, 0, messageKey, 0, 16);
|
|
// copy tag part into authTag
|
|
System.arraycopy(unpackedKey, 16, authTag, 0, 16);
|
|
} else if (element.isKeyTransportElement() && unpackedKey.length == 16) {
|
|
messageKey = unpackedKey;
|
|
} else {
|
|
throw new CryptoFailedException("MessageKey has wrong length: "
|
|
+ unpackedKey.length + ". Probably legacy auth tag format.");
|
|
}
|
|
|
|
return new CipherAndAuthTag(messageKey, element.getHeader().getIv(), authTag, preKey);
|
|
}
|
|
|
|
/**
|
|
* Use the symmetric key in cipherAndAuthTag to decrypt the payload of the omemoMessage.
|
|
* The decrypted payload will be the body of the returned Message.
|
|
*
|
|
* @param element omemoElement containing a payload.
|
|
* @param cipherAndAuthTag cipher and authentication tag.
|
|
* @return decrypted plain text.
|
|
*
|
|
* @throws CryptoFailedException if decryption using AES key fails.
|
|
*/
|
|
static String decryptMessageElement(OmemoElement element, CipherAndAuthTag cipherAndAuthTag)
|
|
throws CryptoFailedException {
|
|
if (!element.isMessageElement()) {
|
|
throw new IllegalArgumentException("decryptMessageElement cannot decrypt OmemoElement which is no MessageElement!");
|
|
}
|
|
|
|
if (cipherAndAuthTag.getAuthTag() == null || cipherAndAuthTag.getAuthTag().length != 16) {
|
|
throw new CryptoFailedException("AuthenticationTag is null or has wrong length: "
|
|
+ (cipherAndAuthTag.getAuthTag() == null ? "null" : cipherAndAuthTag.getAuthTag().length));
|
|
}
|
|
|
|
byte[] encryptedBody = payloadAndAuthTag(element, cipherAndAuthTag.getAuthTag());
|
|
|
|
try {
|
|
return cipherAndAuthTag.decrypt(encryptedBody);
|
|
} catch (IllegalBlockSizeException | BadPaddingException | InvalidKeyException | NoSuchAlgorithmException
|
|
| NoSuchPaddingException | InvalidAlgorithmParameterException e) {
|
|
throw new CryptoFailedException("decryptMessageElement could not decipher message body", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return the concatenation of the payload of the OmemoElement and the given auth tag.
|
|
*
|
|
* @param element omemoElement (message element)
|
|
* @param authTag authTag
|
|
* @return payload + authTag
|
|
*/
|
|
static byte[] payloadAndAuthTag(OmemoElement element, byte[] authTag) {
|
|
if (!element.isMessageElement()) {
|
|
throw new IllegalArgumentException("OmemoElement has no payload.");
|
|
}
|
|
|
|
byte[] payload = new byte[element.getPayload().length + authTag.length];
|
|
System.arraycopy(element.getPayload(), 0, payload, 0, element.getPayload().length);
|
|
System.arraycopy(authTag, 0, payload, element.getPayload().length, authTag.length);
|
|
return payload;
|
|
}
|
|
|
|
}
|