Mercury-IM/domain/src/main/java/org/jivesoftware/smackx/ikey/IkeyManager.java

294 lines
14 KiB
Java
Raw Normal View History

package org.jivesoftware.smackx.ikey;
2020-10-24 19:25:28 +02:00
import org.bouncycastle.openpgp.PGPException;
2020-09-02 16:40:36 +02:00
import org.bouncycastle.openpgp.PGPPublicKeyRing;
2020-10-24 19:25:28 +02:00
import org.bouncycastle.openpgp.PGPSecretKeyRing;
2020-09-02 16:40:36 +02:00
import org.jivesoftware.smack.Manager;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
2020-09-11 14:41:48 +02:00
import org.jivesoftware.smack.packet.Message;
2020-09-25 13:10:02 +02:00
import org.jivesoftware.smack.provider.ProviderManager;
2020-09-02 16:40:36 +02:00
import org.jivesoftware.smackx.ikey.element.IkeyElement;
2020-09-25 22:18:42 +02:00
import org.jivesoftware.smackx.ikey.element.ProofElement;
import org.jivesoftware.smackx.ikey.element.SignedElement;
2020-10-24 19:25:28 +02:00
import org.jivesoftware.smackx.ikey.element.SubordinateElement;
2020-09-25 13:10:02 +02:00
import org.jivesoftware.smackx.ikey.element.SubordinateListElement;
2020-09-25 22:18:42 +02:00
import org.jivesoftware.smackx.ikey.element.SuperordinateElement;
import org.jivesoftware.smackx.ikey.mechanism.IkeySignatureCreationMechanism;
2020-09-02 16:40:36 +02:00
import org.jivesoftware.smackx.ikey.mechanism.IkeySignatureVerificationMechanism;
2020-09-25 13:10:02 +02:00
import org.jivesoftware.smackx.ikey.provider.IkeyElementProvider;
import org.jivesoftware.smackx.ikey.provider.SubordinateListElementProvider;
2020-09-25 12:58:27 +02:00
import org.jivesoftware.smackx.ikey.record.IkeyStore;
2020-09-02 16:40:36 +02:00
import org.jivesoftware.smackx.ikey.util.IkeyConstants;
import org.jivesoftware.smackx.ikey.util.UnsupportedSignatureAlgorithmException;
2020-10-24 19:25:28 +02:00
import org.jivesoftware.smackx.ikey_ox.OxIkeySignatureCreationMechanism;
2020-09-02 16:40:36 +02:00
import org.jivesoftware.smackx.ikey_ox.OxIkeySignatureVerificationMechanism;
2020-10-24 19:25:28 +02:00
import org.jivesoftware.smackx.ox.OpenPgpManager;
import org.jivesoftware.smackx.ox.OpenPgpSecretKeyBackupPassphrase;
import org.jivesoftware.smackx.ox.element.SecretkeyElement;
2020-11-19 23:36:49 +01:00
import org.jivesoftware.smackx.ox.exception.InvalidBackupCodeException;
2020-10-24 19:25:28 +02:00
import org.jivesoftware.smackx.ox.util.OpenPgpPubSubUtil;
import org.jivesoftware.smackx.ox.util.SecretKeyBackupHelper;
2020-09-02 16:40:36 +02:00
import org.jivesoftware.smackx.pep.PepEventListener;
import org.jivesoftware.smackx.pep.PepManager;
import org.jivesoftware.smackx.pubsub.LeafNode;
import org.jivesoftware.smackx.pubsub.PayloadItem;
import org.jivesoftware.smackx.pubsub.PubSubException;
import org.jivesoftware.smackx.pubsub.PubSubManager;
import org.jxmpp.jid.EntityBareJid;
2020-10-24 19:25:28 +02:00
import org.mercury_im.messenger.core.crypto.OpenPgpSecretKeyBackupPassphraseGenerator;
import org.mercury_im.messenger.core.crypto.SecureRandomSecretKeyBackupPassphraseGenerator;
2020-09-02 16:40:36 +02:00
import org.pgpainless.PGPainless;
2020-11-19 23:36:49 +01:00
import org.pgpainless.key.OpenPgpV4Fingerprint;
2020-10-24 19:25:28 +02:00
import org.pgpainless.key.collection.PGPKeyRing;
import org.pgpainless.key.protection.SecretKeyRingProtector;
2020-11-19 23:36:49 +01:00
import org.pgpainless.util.BCUtil;
2020-09-02 16:40:36 +02:00
import java.io.IOException;
2020-10-24 19:25:28 +02:00
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.util.Arrays;
2020-09-02 16:40:36 +02:00
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
2020-09-06 21:41:38 +02:00
import java.util.logging.Level;
import java.util.logging.Logger;
2020-10-24 19:25:28 +02:00
import static org.jivesoftware.smackx.ikey.util.IkeyConstants.SUPERORDINATE_NODE;
2020-09-02 16:40:36 +02:00
public final class IkeyManager extends Manager {
2020-09-06 21:41:38 +02:00
private static final Logger LOGGER = Logger.getLogger(IkeyManager.class.getName());
2020-09-02 16:40:36 +02:00
private static final Map<XMPPConnection, IkeyManager> INSTANCES = new WeakHashMap<>();
2020-10-24 19:25:28 +02:00
private final OpenPgpSecretKeyBackupPassphraseGenerator backupPassphraseGenerator;
2020-09-25 13:10:02 +02:00
static {
// TODO: Replace with .providers file once merged into Smack
ProviderManager.addExtensionProvider(IkeyElement.ELEMENT, IkeyElement.NAMESPACE, new IkeyElementProvider());
ProviderManager.addExtensionProvider(SubordinateListElement.ELEMENT, SubordinateListElement.NAMESPACE, new SubordinateListElementProvider());
}
2020-09-02 16:40:36 +02:00
private IkeyStore store;
private IkeyManager(XMPPConnection connection) {
super(connection);
2020-10-24 19:25:28 +02:00
this.backupPassphraseGenerator = new SecureRandomSecretKeyBackupPassphraseGenerator();
2020-09-02 16:40:36 +02:00
}
public static synchronized IkeyManager getInstanceFor(XMPPConnection connection) {
IkeyManager manager = INSTANCES.get(connection);
if (manager == null) {
manager = new IkeyManager(connection);
INSTANCES.put(connection, manager);
}
return manager;
}
2020-10-24 19:25:28 +02:00
public SecretkeyElement fetchSecretIdentityKey()
throws InterruptedException, PubSubException.NotALeafNodeException,
XMPPException.XMPPErrorException, SmackException.NotConnectedException,
SmackException.NoResponseException {
return OpenPgpPubSubUtil.fetchSecretKey(PepManager.getInstanceFor(connection()), SUPERORDINATE_NODE);
}
public OpenPgpSecretKeyBackupPassphrase depositIdentityKeyBackup()
throws PGPException, InterruptedException, SmackException.NoResponseException,
SmackException.NotConnectedException, SmackException.FeatureNotSupportedException,
XMPPException.XMPPErrorException, PubSubException.NotALeafNodeException, IOException {
PGPSecretKeyRing secretKeys = store.loadSecretKey();
OpenPgpSecretKeyBackupPassphrase passphrase = store.loadBackupPassphrase();
return depositIdentityKeyBackup(secretKeys, passphrase);
}
public OpenPgpSecretKeyBackupPassphrase depositIdentityKeyBackup(PGPSecretKeyRing secretKey, OpenPgpSecretKeyBackupPassphrase passphrase)
throws InterruptedException, SmackException.NoResponseException,
SmackException.NotConnectedException, SmackException.FeatureNotSupportedException,
XMPPException.XMPPErrorException, PubSubException.NotALeafNodeException, IOException, PGPException {
SecretkeyElement secretkeyElement = SecretKeyBackupHelper.createSecretkeyElement(secretKey.getEncoded(), passphrase);
OpenPgpPubSubUtil.depositSecretKey(connection(), secretkeyElement, SUPERORDINATE_NODE);
return passphrase;
}
public IkeyElement createOxIkeyElement(PGPSecretKeyRing secretKeys,
SecretKeyRingProtector keyRingProtector,
SubordinateElement... subordinateElements) throws IOException {
IkeySignatureCreationMechanism mechanism = new OxIkeySignatureCreationMechanism(secretKeys, keyRingProtector);
SuperordinateElement superordinateElement = new SuperordinateElement(secretKeys.getPublicKey().getEncoded());
SubordinateListElement subordinateListElement = new SubordinateListElement(connection().getUser().asEntityBareJid(),
new Date(), Arrays.asList(subordinateElements));
return createIkeyElement(mechanism, superordinateElement, subordinateListElement);
}
public boolean deleteSecretIdentityKeyNode()
throws XMPPException.XMPPErrorException, SmackException.NotConnectedException,
InterruptedException, SmackException.NoResponseException {
return OpenPgpPubSubUtil.deleteSecretKeyNode(PepManager.getInstanceFor(connection()), SUPERORDINATE_NODE);
}
2020-09-02 16:40:36 +02:00
public void startListeners() {
PepManager.getInstanceFor(connection())
2020-10-11 11:44:47 +02:00
.addPepEventListener(IkeyConstants.SUBORDINATES_NODE, IkeyElement.class, ikeyPepEventListener);
2020-09-02 16:40:36 +02:00
}
public void stopListeners() {
PepManager.getInstanceFor(connection())
2020-10-11 11:44:47 +02:00
.removePepEventListener(ikeyPepEventListener);
}
public void setStore(IkeyStore store) {
this.store = store;
2020-09-02 16:40:36 +02:00
}
2020-09-25 22:18:42 +02:00
public IkeyElement createIkeyElement(IkeySignatureCreationMechanism mechanism,
SuperordinateElement superordinateElement,
SubordinateListElement subordinateListElement)
throws IOException {
SignedElement signedElement = new SignedElement(subordinateListElement);
ProofElement proofElement = new ProofElement(mechanism.createSignature(signedElement.getUtf8Bytes()));
return new IkeyElement(mechanism.getType(), superordinateElement, signedElement, proofElement);
}
2020-09-02 16:40:36 +02:00
public void publishIkeyElement(IkeyElement ikeyElement)
throws InterruptedException, PubSubException.NotALeafNodeException, XMPPException.XMPPErrorException,
SmackException.NotConnectedException, SmackException.NoResponseException {
PepManager.getInstanceFor(connection())
2020-09-28 13:33:51 +02:00
.publish(IkeyConstants.SUBORDINATES_NODE, new PayloadItem<>(ikeyElement));
2020-09-02 16:40:36 +02:00
}
public IkeyElement fetchIkeyElementOf(EntityBareJid jid)
throws InterruptedException, PubSubException.NotALeafNodeException, SmackException.NoResponseException,
SmackException.NotConnectedException, XMPPException.XMPPErrorException, PubSubException.NotAPubSubNodeException {
PubSubManager pubSubManager = PubSubManager.getInstanceFor(connection(), jid);
return fetchIkeyElementFrom(pubSubManager);
}
public IkeyElement fetchOwnIkeyElement()
throws InterruptedException, PubSubException.NotALeafNodeException, SmackException.NoResponseException,
SmackException.NotConnectedException, XMPPException.XMPPErrorException, PubSubException.NotAPubSubNodeException {
PubSubManager pubSubManager = PubSubManager.getInstanceFor(connection());
return fetchIkeyElementFrom(pubSubManager);
}
2020-09-02 16:40:36 +02:00
2020-09-11 14:41:48 +02:00
private static IkeyElement fetchIkeyElementFrom(PubSubManager pubSubManager)
2020-09-06 21:41:38 +02:00
throws PubSubException.NotALeafNodeException, SmackException.NoResponseException,
SmackException.NotConnectedException, InterruptedException, XMPPException.XMPPErrorException,
PubSubException.NotAPubSubNodeException {
2020-09-28 13:33:51 +02:00
LeafNode node = pubSubManager.getLeafNode(IkeyConstants.SUBORDINATES_NODE);
2020-09-02 16:40:36 +02:00
List<PayloadItem<IkeyElement>> items = node.getItems(1);
if (items.isEmpty()) {
return null;
} else {
return items.get(0).getPayload();
}
}
private void processIkeyElement(EntityBareJid from, IkeyElement element)
2020-09-25 12:58:27 +02:00
throws IOException, UnsupportedSignatureAlgorithmException {
2020-09-06 21:41:38 +02:00
if (isFromTheFuture(element)) {
2020-09-25 12:58:27 +02:00
LOGGER.log(Level.WARNING, "Received ikey element appears to be from the future: " + element.getSignedElement().getChildElement().getTimestamp());
2020-09-06 21:41:38 +02:00
return;
}
if (existsSameOrNewerRecord(element)) {
LOGGER.log(Level.WARNING, "There exists this exact, or a newer ikey record in the database for " + from);
2020-09-02 16:40:36 +02:00
return;
}
if (!verifyIkeyElement(from, element)) {
2020-09-06 21:41:38 +02:00
LOGGER.log(Level.WARNING, "Invalid signature on ikey element of " + from);
2020-09-02 16:40:36 +02:00
return;
}
2020-09-06 21:41:38 +02:00
store.storeIkeyRecord(from, element);
2020-09-02 16:40:36 +02:00
}
2020-10-11 11:44:47 +02:00
public IkeyElement getIkeyElementOf(EntityBareJid from) throws IOException, InterruptedException, PubSubException.NotALeafNodeException, SmackException.NoResponseException, SmackException.NotConnectedException, XMPPException.XMPPErrorException, PubSubException.NotAPubSubNodeException {
IkeyElement stored = store.loadIkeyRecord(from);
if (stored == null) {
stored = fetchIkeyElementOf(from);
store.storeIkeyRecord(from, stored);
}
return stored;
}
2020-09-02 16:40:36 +02:00
private boolean verifyIkeyElement(EntityBareJid from, IkeyElement element)
2020-09-25 12:58:27 +02:00
throws IOException, UnsupportedSignatureAlgorithmException {
2020-09-02 16:40:36 +02:00
IkeySignatureVerificationMechanism verificationMechanism = getSignatureVerificationMechanismFor(element);
2020-09-25 12:58:27 +02:00
IkeySignatureVerifier verifier = new IkeySignatureVerifier(verificationMechanism);
2020-09-02 16:40:36 +02:00
return verifier.verify(element, from);
}
2020-09-11 14:41:48 +02:00
private static IkeySignatureVerificationMechanism getSignatureVerificationMechanismFor(IkeyElement ikeyElement)
2020-09-02 16:40:36 +02:00
throws IOException, UnsupportedSignatureAlgorithmException {
switch (ikeyElement.getType()) {
case OX:
PGPPublicKeyRing ikey = PGPainless.readKeyRing().publicKeyRing(ikeyElement.getSuperordinate().getPubKeyBytes());
return new OxIkeySignatureVerificationMechanism(ikey);
2020-10-24 19:25:28 +02:00
default:
throw new UnsupportedSignatureAlgorithmException(ikeyElement.getType().name());
2020-09-02 16:40:36 +02:00
}
}
private static boolean isFromTheFuture(IkeyElement element) {
2020-09-25 12:58:27 +02:00
Date elementTimestamp = element.getSignedElement().getChildElement().getTimestamp();
2020-09-02 16:40:36 +02:00
Date now = new Date();
return elementTimestamp.after(now);
}
2020-09-11 14:41:48 +02:00
private boolean existsSameOrNewerRecord(IkeyElement ikeyElement) throws IOException {
2020-09-25 12:58:27 +02:00
IkeyElement existingRecord = store.loadIkeyRecord(ikeyElement.getSignedElement().getChildElement().getJid());
2020-09-06 21:41:38 +02:00
if (existingRecord == null) {
2020-09-02 16:40:36 +02:00
return false;
}
2020-09-25 12:58:27 +02:00
Date latestTimestamp = existingRecord.getSignedElement().getChildElement().getTimestamp();
Date eventTimestamp = ikeyElement.getSignedElement().getChildElement().getTimestamp();
2020-09-02 16:40:36 +02:00
return latestTimestamp.equals(eventTimestamp) // same
|| latestTimestamp.after(eventTimestamp); // newer
}
2020-09-11 14:41:48 +02:00
@SuppressWarnings("UnnecessaryAnonymousClass")
2020-10-11 11:44:47 +02:00
private final PepEventListener<IkeyElement> ikeyPepEventListener = new PepEventListener<IkeyElement>() {
2020-09-11 14:41:48 +02:00
@Override
public void onPepEvent(EntityBareJid from, IkeyElement event, String id, Message carrierMessage) {
try {
processIkeyElement(from, event);
2020-09-25 12:58:27 +02:00
} catch (IOException | UnsupportedSignatureAlgorithmException e) {
2020-09-11 14:41:48 +02:00
LOGGER.log(Level.WARNING, "Error:", e);
}
}
};
2020-10-24 19:25:28 +02:00
public boolean hasStore() {
return store != null;
}
public boolean hasLocalKey() {
return store.loadSecretKey() != null;
}
public void generateIdentityKey() throws PGPException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException {
PGPKeyRing key = OpenPgpManager.getInstanceFor(connection())
.generateKeyRing(connection().getUser().asBareJid());
store.storeSecretKey(key.getSecretKeys());
store.storeBackupPassphrase(generateBackupPassphrase());
}
private OpenPgpSecretKeyBackupPassphrase generateBackupPassphrase() {
return backupPassphraseGenerator.generateBackupPassphrase();
}
2020-11-19 23:36:49 +01:00
public void restoreSecretKeyBackup(SecretkeyElement secretkeyElement, OpenPgpSecretKeyBackupPassphrase passphrase)
throws PGPException, IOException, InvalidBackupCodeException {
PGPSecretKeyRing secretKeys = SecretKeyBackupHelper.restoreSecretKeyBackup(secretkeyElement, passphrase);
store.storeSecretKey(secretKeys);
store.storeBackupPassphrase(passphrase);
}
}