OmemoKeyUtil.java
/**
*
* Copyright 2017 Paul Schaub
*
* 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.util;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jivesoftware.smackx.omemo.OmemoFingerprint;
import org.jivesoftware.smackx.omemo.OmemoManager;
import org.jivesoftware.smackx.omemo.OmemoStore;
import org.jivesoftware.smackx.omemo.element.OmemoBundleVAxolotlElement;
import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
import org.jivesoftware.smackx.omemo.internal.OmemoSession;
import org.jxmpp.stringprep.XmppStringprepException;
/**
* Class that is used to convert bytes to keys and vice versa.
*
* @param <T_IdKeyPair> IdentityKeyPair class
* @param <T_IdKey> IdentityKey class
* @param <T_PreKey> PreKey class
* @param <T_SigPreKey> SignedPreKey class
* @param <T_Sess> Session class
* @param <T_Addr> Address class
* @param <T_ECPub> Elliptic Curve PublicKey class
* @param <T_Bundle> Bundle class
* @param <T_Ciph> Cipher class
* @author Paul Schaub
*/
public abstract class OmemoKeyUtil<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(OmemoKeyUtil.class.getName());
public final Bundle BUNDLE = new Bundle();
/**
* Bundle related methods.
*/
public class Bundle {
/**
* Extract an IdentityKey from a OmemoBundleElement.
*
* @param bundle OmemoBundleElement
* @return identityKey
* @throws CorruptedOmemoKeyException if the key is damaged/malformed
*/
public T_IdKey identityKey(OmemoBundleVAxolotlElement bundle) throws CorruptedOmemoKeyException {
return identityKeyFromBytes(bundle.getIdentityKey());
}
/**
* Extract a signedPreKey from an OmemoBundleElement.
*
* @param bundle OmemoBundleElement
* @return singedPreKey
* @throws CorruptedOmemoKeyException if the key is damaged/malformed
*/
public T_ECPub signedPreKeyPublic(OmemoBundleVAxolotlElement bundle) throws CorruptedOmemoKeyException {
return signedPreKeyPublicFromBytes(bundle.getSignedPreKey());
}
/**
* Extract the id of the transported signedPreKey from the bundle.
*
* @param bundle OmemoBundleElement
* @return signedPreKeyId
*/
public int signedPreKeyId(OmemoBundleVAxolotlElement bundle) {
return bundle.getSignedPreKeyId();
}
/**
* Extract the signature of the signedPreKey in the bundle as a byte array.
*
* @param bundle OmemoBundleElement
* @return signature
*/
public byte[] signedPreKeySignature(OmemoBundleVAxolotlElement bundle) {
return bundle.getSignedPreKeySignature();
}
/**
* Extract the preKey with id 'keyId' from the bundle.
*
* @param bundle OmemoBundleElement
* @param keyId id of the preKey
* @return the preKey
* @throws CorruptedOmemoKeyException when the key cannot be parsed from bytes
*/
public T_ECPub preKeyPublic(OmemoBundleVAxolotlElement bundle, int keyId) throws CorruptedOmemoKeyException {
return preKeyPublicFromBytes(bundle.getPreKey(keyId));
}
/**
* Break up the OmemoBundleElement into a list of crypto-lib specific bundles (T_PreKey).
* In case of the signal library, we break the OmemoBundleElement in ~100 PreKeyBundles (one for every transported
* preKey).
*
* @param bundle OmemoBundleElement containing multiple PreKeys
* @param contact Contact that the bundle belongs to
* @return a HashMap with one T_Bundle per preKey and the preKeyId as key
* @throws CorruptedOmemoKeyException when one of the keys cannot be parsed
*/
public HashMap<Integer, T_Bundle> bundles(OmemoBundleVAxolotlElement bundle, OmemoDevice contact) throws CorruptedOmemoKeyException {
HashMap<Integer, T_Bundle> bundles = new HashMap<>();
for (int deviceId : bundle.getPreKeys().keySet()) {
try {
bundles.put(deviceId, bundleFromOmemoBundle(bundle, contact, deviceId));
} catch (CorruptedOmemoKeyException e) {
LOGGER.log(Level.INFO, "Cannot parse PreKeyBundle: " + e.getMessage());
}
}
if (bundles.size() == 0) {
throw new CorruptedOmemoKeyException("Bundle contained no valid preKeys.");
}
return bundles;
}
}
/**
* Deserialize an identityKeyPair from a byte array.
*
* @param data byte array
* @return IdentityKeyPair (T_IdKeyPair)
* @throws CorruptedOmemoKeyException if the key is damaged of malformed
*/
public abstract T_IdKeyPair identityKeyPairFromBytes(byte[] data) throws CorruptedOmemoKeyException;
/**
* Deserialize an identityKey from a byte array.
*
* @param data byte array
* @return identityKey (T_IdKey)
* @throws CorruptedOmemoKeyException if the key is damaged or malformed
*/
public abstract T_IdKey identityKeyFromBytes(byte[] data) throws CorruptedOmemoKeyException;
/**
* Serialize an identityKey into bytes.
*
* @param identityKey idKey
* @return bytes
*/
public abstract byte[] identityKeyToBytes(T_IdKey identityKey);
/**
* Deserialize an elliptic curve public key from bytes.
*
* @param data bytes
* @return elliptic curve public key (T_ECPub)
* @throws CorruptedOmemoKeyException if the key is damaged or malformed
*/
public abstract T_ECPub ellipticCurvePublicKeyFromBytes(byte[] data) throws CorruptedOmemoKeyException;
/**
* Deserialize a public preKey from bytes.
*
* @param data preKey as bytes
* @return preKey
* @throws CorruptedOmemoKeyException if the key is damaged or malformed
*/
public T_ECPub preKeyPublicFromBytes(byte[] data) throws CorruptedOmemoKeyException {
return ellipticCurvePublicKeyFromBytes(data);
}
/**
* Serialize a preKey into a byte array.
*
* @param preKey preKey
* @return byte[]
*/
public abstract byte[] preKeyToBytes(T_PreKey preKey);
/**
* Deserialize a preKey from a byte array.
*
* @param bytes byte array
* @return deserialized preKey
* @throws IOException when something goes wrong
*/
public abstract T_PreKey preKeyFromBytes(byte[] bytes) throws IOException;
/**
* Generate 'count' new PreKeys beginning with id 'startId'.
* These preKeys are published and can be used by contacts to establish sessions with us.
*
* @param startId start id
* @param count how many keys do we want to generate
* @return Map of new preKeys
*/
public abstract HashMap<Integer, T_PreKey> generateOmemoPreKeys(int startId, int count);
/**
* Generate a new signed preKey.
*
* @param identityKeyPair identityKeyPair used to sign the preKey
* @param signedPreKeyId id that the preKey will have
* @return signedPreKey
* @throws CorruptedOmemoKeyException when the identityKeyPair is invalid
*/
public abstract T_SigPreKey generateOmemoSignedPreKey(T_IdKeyPair identityKeyPair, int signedPreKeyId) throws CorruptedOmemoKeyException;
/**
* Deserialize a public signedPreKey from bytes.
*
* @param data bytes
* @return signedPreKey
* @throws CorruptedOmemoKeyException if the key is damaged or malformed
*/
public T_ECPub signedPreKeyPublicFromBytes(byte[] data) throws CorruptedOmemoKeyException {
return ellipticCurvePublicKeyFromBytes(data);
}
/**
* Deserialize a signedPreKey from a byte array.
*
* @param data byte array
* @return deserialized signed preKey
* @throws IOException when something goes wrong
*/
public abstract T_SigPreKey signedPreKeyFromBytes(byte[] data) throws IOException;
/**
* Serialize a signedPreKey into a byte array.
*
* @param sigPreKey signedPreKey
* @return byte array
*/
public abstract byte[] signedPreKeyToBytes(T_SigPreKey sigPreKey);
/**
* Build a crypto-lib specific PreKeyBundle (T_Bundle) using a PreKey from the OmemoBundleElement 'bundle'.
* The PreKeyBundle will contain the identityKey, signedPreKey and signature, as well as a preKey
* from the OmemoBundleElement.
*
* @param bundle OmemoBundleElement
* @param contact Contact that the bundle belongs to
* @param keyId id of the preKey that will be selected from the OmemoBundleElement and that the PreKeyBundle will contain
* @return PreKeyBundle (T_PreKey)
* @throws CorruptedOmemoKeyException if some key is damaged or malformed
*/
public abstract T_Bundle bundleFromOmemoBundle(OmemoBundleVAxolotlElement bundle, OmemoDevice contact, int keyId) throws CorruptedOmemoKeyException;
/**
* Extract the signature from a signedPreKey.
*
* @param signedPreKey signedPreKey
* @return signature as byte array
*/
public abstract byte[] signedPreKeySignatureFromKey(T_SigPreKey signedPreKey);
/**
* Generate a new IdentityKeyPair. We should always have only one pair and usually keep this for a long time.
*
* @return identityKeyPair
*/
public abstract T_IdKeyPair generateOmemoIdentityKeyPair();
/**
* return the id of the given signedPreKey.
*
* @param signedPreKey key
* @return id of the key
*/
public abstract int signedPreKeyIdFromKey(T_SigPreKey signedPreKey);
/**
* serialize an identityKeyPair into bytes.
*
* @param identityKeyPair identityKeyPair
* @return byte array
*/
public abstract byte[] identityKeyPairToBytes(T_IdKeyPair identityKeyPair);
/**
* Extract the public identityKey from an identityKeyPair.
*
* @param pair keyPair
* @return public key of the pair
*/
public abstract T_IdKey identityKeyFromPair(T_IdKeyPair pair);
/**
* Prepare an identityKey for transport in an OmemoBundleElement (serialize it).
*
* @param identityKey identityKey that will be transported
* @return key as byte array
*/
public abstract byte[] identityKeyForBundle(T_IdKey identityKey);
/**
* Prepare an elliptic curve preKey for transport in an OmemoBundleElement.
*
* @param preKey key
* @return key as byte array
*/
public abstract byte[] preKeyPublicKeyForBundle(T_ECPub preKey);
/**
* Prepare a preKey for transport in an OmemoBundleElement.
*
* @param preKey preKey
* @return key as byte array
*/
public abstract byte[] preKeyForBundle(T_PreKey preKey);
/**
* Prepare a whole bunche of preKeys for transport.
*
* @param preKeyHashMap HashMap of preKeys
* @return HashMap of byte arrays but with the same keyIds as key
*/
public HashMap<Integer, byte[]> preKeyPublisKeysForBundle(HashMap<Integer, T_PreKey> preKeyHashMap) {
HashMap<Integer, byte[]> out = new HashMap<>();
for (Map.Entry<Integer, T_PreKey> e : preKeyHashMap.entrySet()) {
out.put(e.getKey(), preKeyForBundle(e.getValue()));
}
return out;
}
/**
* Prepare a public signedPreKey for transport in a bundle.
*
* @param signedPreKey signedPrekey
* @return signedPreKey as byte array
*/
public abstract byte[] signedPreKeyPublicForBundle(T_SigPreKey signedPreKey);
/**
* Return the fingerprint of an identityKey.
*
* @param identityKey identityKey
* @return fingerprint of the key
*/
public abstract OmemoFingerprint getFingerprint(T_IdKey identityKey);
/**
* Create a new crypto-specific Session object.
*
* @param omemoManager omemoManager of our device.
* @param omemoStore omemoStore where we can save the session, get keys from etc.
* @param from the device we want to create the session with.
* @return a new session
*/
public abstract OmemoSession<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph>
createOmemoSession(OmemoManager omemoManager, OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> omemoStore,
OmemoDevice from);
/**
* Create a new concrete OmemoSession with a contact.
*
* @param omemoManager omemoManager of our device.
* @param omemoStore omemoStore
* @param device device to establish the session with
* @param identityKey identityKey of the device
* @return concrete OmemoSession
*/
public abstract OmemoSession<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph>
createOmemoSession(OmemoManager omemoManager, OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> omemoStore,
OmemoDevice device, T_IdKey identityKey);
/**
* Deserialize a raw OMEMO Session from bytes.
*
* @param data bytes
* @return raw OMEMO Session
* @throws IOException when something goes wrong
*/
public abstract T_Sess rawSessionFromBytes(byte[] data) throws IOException;
/**
* Serialize a raw OMEMO session into a byte array.
*
* @param session raw session
* @return byte array
*/
public abstract byte[] rawSessionToBytes(T_Sess session);
/**
* Convert an OmemoDevice to a crypto-lib specific contact format.
*
* @param contact omemoContact
* @return crypto-lib specific contact object
*/
public abstract T_Addr omemoDeviceAsAddress(OmemoDevice contact);
/**
* Convert a crypto-lib specific contact object into an OmemoDevice.
*
* @param address contact
* @return as OmemoDevice
* @throws XmppStringprepException if the address is not a valid BareJid
*/
public abstract OmemoDevice addressAsOmemoDevice(T_Addr address) throws XmppStringprepException;
public static String prettyFingerprint(OmemoFingerprint fingerprint) {
return prettyFingerprint(fingerprint.toString());
}
/**
* Split the fingerprint in blocks of 8 characters with spaces between.
*
* @param ugly fingerprint as continuous string
* @return fingerprint with spaces for better readability
*/
public static String prettyFingerprint(String ugly) {
if (ugly == null) return null;
String pretty = "";
for (int i = 0; i < 8; i++) {
if (i != 0) pretty += " ";
pretty += ugly.substring(8 * i, 8 * (i + 1));
}
return pretty;
}
/**
* Add integers modulo MAX_VALUE.
*
* @param value base integer
* @param added value that is added to the base value
* @return (value plus added) modulo Integer.MAX_VALUE
*/
public static int addInBounds(int value, int added) {
int avail = Integer.MAX_VALUE - value;
if (avail < added) {
return added - avail;
} else {
return value + added;
}
}
}