1
0
Fork 0
mirror of https://github.com/vanitasvitae/Smack.git synced 2025-12-07 11:31:10 +01:00

WiP, please don't look

This commit is contained in:
Paul Schaub 2018-06-13 18:39:09 +02:00
parent dab342e97e
commit 49a51bfa2d
32 changed files with 996 additions and 1053 deletions

View file

@ -17,6 +17,7 @@
package org.jivesoftware.smackx.ox;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
@ -32,14 +33,18 @@ import org.jivesoftware.smack.chat2.Chat;
import org.jivesoftware.smack.chat2.ChatManager;
import org.jivesoftware.smack.chat2.IncomingChatMessageListener;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.util.stringencoder.Base64;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.ox.chat.OpenPgpEncryptedChat;
import org.jivesoftware.smackx.ox.chat.OpenPgpFingerprints;
import org.jivesoftware.smackx.ox.chat.OpenPgpMessage;
import org.jivesoftware.smackx.ox.element.OpenPgpContentElement;
import org.jivesoftware.smackx.ox.element.OpenPgpElement;
import org.jivesoftware.smackx.ox.element.SigncryptElement;
import org.jivesoftware.smackx.ox.exception.MissingOpenPgpKeyPairException;
import org.jivesoftware.smackx.ox.exception.MissingOpenPgpPublicKeyException;
import org.jivesoftware.smackx.ox.exception.SmackOpenPgpException;
import org.jivesoftware.smackx.ox.listener.OpenPgpEncryptedMessageListener;
import org.jivesoftware.smackx.ox.util.DecryptedBytesAndMetadata;
import org.jxmpp.jid.BareJid;
import org.jxmpp.jid.EntityBareJid;
@ -62,6 +67,7 @@ public final class OXInstantMessagingManager extends Manager {
private final ChatManager chatManager;
private final Set<OpenPgpEncryptedMessageListener> chatMessageListeners = new HashSet<>();
private final Map<BareJid, OpenPgpEncryptedChat> chats = new HashMap<>();
private OXInstantMessagingManager(final XMPPConnection connection) {
super(connection);
@ -144,23 +150,29 @@ public final class OXInstantMessagingManager extends Manager {
try {
OpenPgpEncryptedChat encryptedChat = chatWith(from);
OpenPgpMessage decrypted = provider.decryptAndVerify(element, provider.availableOpenPgpPublicKeysFingerprints(from.asBareJid()));
OpenPgpContentElement contentElement = decrypted.getOpenPgpContentElement();
if (decrypted.getState() != OpenPgpMessage.State.signcrypt) {
DecryptedBytesAndMetadata decryptedBytes = provider.decrypt(Base64.decode(
element.getEncryptedBase64MessageContent()),
from.asBareJid(),
null);
OpenPgpMessage openPgpMessage = new OpenPgpMessage(decryptedBytes.getBytes(),
new OpenPgpMessage.Metadata(decryptedBytes.getDecryptionKey(),
decryptedBytes.getVerifiedSignatures()));
OpenPgpContentElement contentElement = openPgpMessage.getOpenPgpContentElement();
if (openPgpMessage.getState() != OpenPgpMessage.State.signcrypt) {
LOGGER.log(Level.WARNING, "Decrypted content is not a signcrypt element. Ignore it.");
return;
}
SigncryptElement signcryptElement = (SigncryptElement) contentElement;
for (OpenPgpEncryptedMessageListener l : chatMessageListeners) {
l.newIncomingEncryptedMessage(from, message, signcryptElement, encryptedChat);
l.newIncomingOxMessage(from, message, signcryptElement, encryptedChat);
}
} catch (SmackOpenPgpException e) {
LOGGER.log(Level.WARNING, "Could not start chat with " + from, e);
} catch (InterruptedException | XMPPException.XMPPErrorException | SmackException.NotConnectedException | SmackException.NoResponseException e) {
LOGGER.log(Level.WARNING, "Something went wrong.", e);
} catch (MissingOpenPgpPublicKeyException e) {
LOGGER.log(Level.WARNING, "Could not verify message " + message.getStanzaId() + ": Missing senders public key " + e.getFingerprint().toString(), e);
} catch (MissingOpenPgpKeyPairException e) {
LOGGER.log(Level.WARNING, "Could not decrypt message " + message.getStanzaId() + ": Missing secret key", e);
} catch (XmlPullParserException | IOException e) {

View file

@ -16,14 +16,18 @@
*/
package org.jivesoftware.smackx.ox;
import static org.jivesoftware.smackx.ox.PubSubDelegate.PEP_NODE_PUBLIC_KEYS;
import static org.jivesoftware.smackx.ox.PubSubDelegate.PEP_NODE_PUBLIC_KEYS_NOTIFY;
import static org.jivesoftware.smackx.ox.PubSubDelegate.fetchPubkey;
import static org.jivesoftware.smackx.ox.PubSubDelegate.publishPublicKey;
import static org.jivesoftware.smackx.ox.util.PubSubDelegate.PEP_NODE_PUBLIC_KEYS;
import static org.jivesoftware.smackx.ox.util.PubSubDelegate.PEP_NODE_PUBLIC_KEYS_NOTIFY;
import static org.jivesoftware.smackx.ox.util.PubSubDelegate.fetchPubkey;
import static org.jivesoftware.smackx.ox.util.PubSubDelegate.publishPublicKey;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@ -37,18 +41,23 @@ import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.util.Async;
import org.jivesoftware.smack.util.stringencoder.Base64;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.ox.callback.AskForBackupCodeCallback;
import org.jivesoftware.smackx.ox.callback.DisplayBackupCodeCallback;
import org.jivesoftware.smackx.ox.callback.SecretKeyBackupSelectionCallback;
import org.jivesoftware.smackx.ox.callback.SecretKeyRestoreSelectionCallback;
import org.jivesoftware.smackx.ox.chat.OpenPgpFingerprints;
import org.jivesoftware.smackx.ox.element.PubkeyElement;
import org.jivesoftware.smackx.ox.element.PublicKeysListElement;
import org.jivesoftware.smackx.ox.element.SecretkeyElement;
import org.jivesoftware.smackx.ox.exception.InvalidBackupCodeException;
import org.jivesoftware.smackx.ox.exception.MissingOpenPgpKeyPairException;
import org.jivesoftware.smackx.ox.exception.MissingOpenPgpPublicKeyException;
import org.jivesoftware.smackx.ox.exception.MissingUserIdOnKeyException;
import org.jivesoftware.smackx.ox.exception.SmackOpenPgpException;
import org.jivesoftware.smackx.ox.util.KeyBytesAndFingerprint;
import org.jivesoftware.smackx.ox.util.PubSubDelegate;
import org.jivesoftware.smackx.pep.PEPListener;
import org.jivesoftware.smackx.pep.PEPManager;
import org.jivesoftware.smackx.pubsub.EventElement;
@ -132,20 +141,35 @@ public final class OpenPgpManager extends Manager {
* @throws SmackException.NotConnectedException
* @throws SmackException.NoResponseException
*/
public void announceSupportAndPublish() throws NoSuchAlgorithmException, NoSuchProviderException, SmackOpenPgpException,
public void announceSupportAndPublish()
throws NoSuchAlgorithmException, NoSuchProviderException, SmackOpenPgpException,
InterruptedException, PubSubException.NotALeafNodeException, XMPPException.XMPPErrorException,
SmackException.NotConnectedException, SmackException.NoResponseException {
SmackException.NotConnectedException, SmackException.NoResponseException, IOException,
InvalidAlgorithmParameterException, SmackException.NotLoggedInException {
throwIfNoProviderSet();
throwIfNotAuthenticated();
BareJid ourJid = connection().getUser().asBareJid();
OpenPgpV4Fingerprint primaryFingerprint = getOurFingerprint();
OpenPgpV4Fingerprint primaryFingerprint = provider.primaryOpenPgpKeyPairFingerprint();
if (primaryFingerprint == null) {
primaryFingerprint = provider.createOpenPgpKeyPair();
KeyBytesAndFingerprint bytesAndFingerprint = provider.generateOpenPgpKeyPair(ourJid);
primaryFingerprint = bytesAndFingerprint.getFingerprint();
// This should never throw, since we set our jid literally one line above this comment.
try {
provider.importSecretKey(ourJid, bytesAndFingerprint.getBytes());
} catch (MissingUserIdOnKeyException e) {
throw new AssertionError(e);
}
}
// Create <pubkey/> element
PubkeyElement pubkeyElement;
try {
pubkeyElement = provider.createPubkeyElement(primaryFingerprint);
pubkeyElement = createPubkeyElement(ourJid, primaryFingerprint, new Date());
} catch (MissingOpenPgpPublicKeyException e) {
throw new AssertionError("Cannot publish our public key, since it is missing (MUST NOT happen!)");
}
@ -166,7 +190,7 @@ public final class OpenPgpManager extends Manager {
*/
public OpenPgpV4Fingerprint getOurFingerprint() {
throwIfNoProviderSet();
return provider.primaryOpenPgpKeyPairFingerprint();
return provider.getStore().getPrimaryOpenPgpKeyPairFingerprint();
}
/**
@ -183,7 +207,8 @@ public final class OpenPgpManager extends Manager {
*/
public boolean serverSupportsSecretKeyBackups()
throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
SmackException.NoResponseException {
SmackException.NoResponseException, SmackException.NotLoggedInException {
throwIfNotAuthenticated();
boolean pep = PEPManager.getInstanceFor(connection()).isSupported();
boolean whitelist = PubSubManager.getInstance(connection(), connection().getUser().asBareJid())
.getSupportedFeatures().containsFeature("http://jabber.org/protocol/pubsub#access-whitelist");
@ -197,7 +222,6 @@ public final class OpenPgpManager extends Manager {
*
* @param displayCodeCallback callback, which will receive the backup password used to encrypt the secret key.
* @param selectKeyCallback callback, which will receive the users choice of which keys will be backed up.
* @throws SmackOpenPgpException if the secret key is corrupted or can for some reason not be serialized.
* @throws InterruptedException
* @throws PubSubException.NotALeafNodeException
* @throws XMPPException.XMPPErrorException
@ -206,14 +230,20 @@ public final class OpenPgpManager extends Manager {
*/
public void backupSecretKeyToServer(DisplayBackupCodeCallback displayCodeCallback,
SecretKeyBackupSelectionCallback selectKeyCallback)
throws SmackOpenPgpException, InterruptedException, PubSubException.NotALeafNodeException,
throws InterruptedException, PubSubException.NotALeafNodeException,
XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException,
MissingOpenPgpKeyPairException {
SmackException.NotLoggedInException {
throwIfNoProviderSet();
throwIfNotAuthenticated();
BareJid ownJid = connection().getUser().asBareJid();
String backupCode = generateBackupPassword();
Set<OpenPgpV4Fingerprint> availableKeyPairs = provider.availableOpenPgpKeyPairFingerprints();
SecretkeyElement secretKey = provider.createSecretkeyElement(
selectKeyCallback.selectKeysToBackup(availableKeyPairs), backupCode);
Set<OpenPgpV4Fingerprint> availableKeyPairs = provider.getStore().getAvailableKeyPairFingerprints();
Set<OpenPgpV4Fingerprint> selectedKeyPairs = selectKeyCallback.selectKeysToBackup(availableKeyPairs);
SecretkeyElement secretKey = createSecretkeyElement(ownJid, selectedKeyPairs, backupCode);
PubSubDelegate.depositSecretKey(connection(), secretKey);
displayCodeCallback.displayBackupCode(backupCode);
}
@ -228,7 +258,8 @@ public final class OpenPgpManager extends Manager {
*/
public void deleteSecretKeyServerBackup()
throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
SmackException.NoResponseException {
SmackException.NoResponseException, SmackException.NotLoggedInException {
throwIfNotAuthenticated();
PubSubDelegate.deleteSecretKeyNode(connection());
}
@ -249,10 +280,16 @@ public final class OpenPgpManager extends Manager {
SecretKeyRestoreSelectionCallback selectionCallback)
throws InterruptedException, PubSubException.NotALeafNodeException, XMPPException.XMPPErrorException,
SmackException.NotConnectedException, SmackException.NoResponseException, SmackOpenPgpException,
InvalidBackupCodeException {
InvalidBackupCodeException, SmackException.NotLoggedInException {
throwIfNoProviderSet();
throwIfNotAuthenticated();
SecretkeyElement backup = PubSubDelegate.fetchSecretKey(connection());
provider.restoreSecretKeyBackup(backup, codeCallback.askForBackupCode(), selectionCallback);
if (backup == null) {
// TODO
return;
}
byte[] encrypted = Base64.decode(backup.getB64Data());
// provider.restoreSecretKeyBackup(backup, codeCallback.askForBackupCode(), selectionCallback);
// TODO: catch InvalidBackupCodeException in order to prevent re-fetching the backup on next try.
}
@ -270,22 +307,28 @@ public final class OpenPgpManager extends Manager {
public OpenPgpFingerprints determineContactsKeys(BareJid jid)
throws SmackOpenPgpException, InterruptedException, XMPPException.XMPPErrorException,
SmackException.NotConnectedException, SmackException.NoResponseException {
Set<OpenPgpV4Fingerprint> announced = provider.announcedOpenPgpKeyFingerprints(jid);
Set<OpenPgpV4Fingerprint> available = provider.availableOpenPgpPublicKeysFingerprints(jid);
Set<OpenPgpV4Fingerprint> announced = provider.getStore().getAnnouncedKeysFingerprints(jid).keySet();
Set<OpenPgpV4Fingerprint> available = provider.getStore().getAvailableKeysFingerprints(jid).keySet();
Map<OpenPgpV4Fingerprint, Throwable> unfetched = new HashMap<>();
for (OpenPgpV4Fingerprint f : announced) {
if (!available.contains(f)) {
try {
PubkeyElement pubkeyElement = PubSubDelegate.fetchPubkey(connection(), jid, f);
provider.storePublicKey(jid, f, pubkeyElement);
if (pubkeyElement == null) {
continue;
}
processPublicKey(pubkeyElement, jid);
available.add(f);
} catch (PubSubException.NotAPubSubNodeException | PubSubException.NotALeafNodeException e) {
LOGGER.log(Level.WARNING, "Could not fetch public key " + f.toString() + " of user " + jid.toString(), e);
unfetched.put(f, e);
} catch (MissingUserIdOnKeyException e) {
LOGGER.log(Level.WARNING, "Key does not contain user-id of " + jid + ". Ignoring the key.", e);
unfetched.put(f, e);
}
}
}
return new OpenPgpFingerprints(announced, available, unfetched);
return new OpenPgpFingerprints(jid, announced, available, unfetched);
}
/**
@ -295,7 +338,7 @@ public final class OpenPgpManager extends Manager {
*/
private final PEPListener metadataListener = new PEPListener() {
@Override
public void eventReceived(final EntityBareJid from, final EventElement event, Message message) {
public void eventReceived(final EntityBareJid from, final EventElement event, final Message message) {
if (PEP_NODE_PUBLIC_KEYS.equals(event.getEvent().getNode())) {
final BareJid contact = from.asBareJid();
LOGGER.log(Level.INFO, "Received OpenPGP metadata update from " + contact);
@ -305,17 +348,22 @@ public final class OpenPgpManager extends Manager {
ItemsExtension items = (ItemsExtension) event.getExtensions().get(0);
PayloadItem<?> payload = (PayloadItem) items.getItems().get(0);
PublicKeysListElement listElement = (PublicKeysListElement) payload.getPayload();
provider.storePublicKeysList(connection(), listElement, contact);
Map<OpenPgpV4Fingerprint, Date> announcedKeys = new HashMap<>();
for (OpenPgpV4Fingerprint f : listElement.getMetadata().keySet()) {
PublicKeysListElement.PubkeyMetadataElement meta = listElement.getMetadata().get(f);
announcedKeys.put(meta.getV4Fingerprint(), meta.getDate());
}
provider.getStore().setAnnouncedKeysFingerprints(contact, announcedKeys);
Set<OpenPgpV4Fingerprint> missingKeys = listElement.getMetadata().keySet();
try {
provider.storePublicKeysList(connection(), listElement, contact);
missingKeys.removeAll(provider.availableOpenPgpPublicKeysFingerprints(contact));
missingKeys.removeAll(provider.getStore().getAvailableKeysFingerprints(contact).keySet());
for (OpenPgpV4Fingerprint missing : missingKeys) {
try {
PubkeyElement pubkeyElement = fetchPubkey(connection(), contact, missing);
provider.storePublicKey(contact, missing, pubkeyElement);
processPublicKey(pubkeyElement, contact);
} catch (Exception e) {
LOGGER.log(Level.WARNING, "Error fetching missing OpenPGP key " + missing.toString(), e);
}
@ -329,6 +377,16 @@ public final class OpenPgpManager extends Manager {
}
};
/*
Private stuff.
*/
private void processPublicKey(PubkeyElement pubkeyElement, BareJid owner)
throws MissingUserIdOnKeyException {
byte[] base64 = pubkeyElement.getDataElement().getB64Data();
provider.importPublicKey(owner, Base64.decode(base64));
}
/**
* Generate a secure backup code.
*
@ -357,6 +415,39 @@ public final class OpenPgpManager extends Manager {
return code.toString();
}
private PubkeyElement createPubkeyElement(BareJid owner,
OpenPgpV4Fingerprint fingerprint,
Date date)
throws MissingOpenPgpPublicKeyException {
byte[] keyBytes = provider.getStore().getPublicKeyBytes(owner, fingerprint);
return createPubkeyElement(keyBytes, date);
}
private static PubkeyElement createPubkeyElement(byte[] bytes, Date date) {
return new PubkeyElement(new PubkeyElement.PubkeyDataElement(Base64.encode(bytes)), date);
}
private SecretkeyElement createSecretkeyElement(BareJid owner,
Set<OpenPgpV4Fingerprint> fingerprints,
String backupCode) {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
for (OpenPgpV4Fingerprint fingerprint : fingerprints) {
try {
byte[] bytes = provider.getStore().getSecretKeyBytes(owner, fingerprint);
buffer.write(bytes);
} catch (MissingOpenPgpKeyPairException | IOException e) {
LOGGER.log(Level.WARNING, "Cannot backup secret key " + Long.toHexString(fingerprint.getKeyId()) + ".", e);
}
}
return createSecretkeyElement(buffer.toByteArray(), backupCode);
}
private SecretkeyElement createSecretkeyElement(byte[] keys, String backupCode) {
byte[] encrypted = provider.symmetricallyEncryptWithPassword(keys, backupCode);
return new SecretkeyElement(Base64.encode(encrypted));
}
/**
* Throw an {@link IllegalStateException} if no {@link OpenPgpProvider} is set.
* The OpenPgpProvider is used to process information related to RFC-4880.
@ -366,4 +457,10 @@ public final class OpenPgpManager extends Manager {
throw new IllegalStateException("No OpenPgpProvider set!");
}
}
private void throwIfNotAuthenticated() throws SmackException.NotLoggedInException {
if (!connection().isAuthenticated()) {
throw new SmackException.NotLoggedInException();
}
}
}

View file

@ -16,122 +16,126 @@
*/
package org.jivesoftware.smackx.ox;
import java.util.Set;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import org.jivesoftware.smack.util.MultiMap;
import org.jivesoftware.smackx.ox.callback.SmackMissingOpenPgpPublicKeyCallback;
import org.jivesoftware.smackx.ox.element.CryptElement;
import org.jivesoftware.smackx.ox.element.OpenPgpContentElement;
import org.jivesoftware.smackx.ox.element.OpenPgpElement;
import org.jivesoftware.smackx.ox.element.SignElement;
import org.jivesoftware.smackx.ox.element.SigncryptElement;
import org.jivesoftware.smackx.ox.exception.MissingOpenPgpKeyPairException;
import org.jivesoftware.smackx.ox.exception.MissingOpenPgpPublicKeyException;
import org.jivesoftware.smackx.ox.exception.MissingUserIdOnKeyException;
import org.jivesoftware.smackx.ox.exception.SmackOpenPgpException;
import org.jivesoftware.smackx.ox.util.DecryptedBytesAndMetadata;
import org.jivesoftware.smackx.ox.util.KeyBytesAndFingerprint;
public interface OpenPgpProvider extends OpenPgpStore {
import org.jxmpp.jid.BareJid;
public interface OpenPgpProvider {
/**
* Sign and encrypt a {@link SigncryptElement} element for usage within the context of instant messaging.
* The resulting {@link OpenPgpElement} contains a Base64 encoded, unarmored OpenPGP message,
* which can be decrypted by each recipient, as well as by ourselves.
* The resulting byte array can be decrypted by each recipient, as well as all devices of the user.
* The message contains a signature made by our key.
*
* @see <a href="https://xmpp.org/extensions/xep-0373.html#signcrypt">XEP-0373 §3</a>
* @see <a href="https://xmpp.org/extensions/xep-0374.html#openpgp-secured-im">XEP-0374 §2.1</a>
*
* @param element {@link SigncryptElement} which contains the content of the message as plaintext.
* @param signingKey {@link OpenPgpV4Fingerprint} of the signing key.
* @param encryptionKeys {@link Set} containing all {@link OpenPgpV4Fingerprint}s of keys which will
* @param encryptionKeys {@link MultiMap} containing all {@link OpenPgpV4Fingerprint}s of recipients which will
* be able to decrypt the message.
* @return encrypted {@link OpenPgpElement} which contains the encrypted, encoded message.
* @return encrypted and signed data which contains the encrypted, encoded message.
*
* @throws MissingOpenPgpKeyPairException if the OpenPGP key pair with the given {@link OpenPgpV4Fingerprint}
* is not available.
* @throws MissingOpenPgpKeyPairException if any of the OpenPGP public keys whose {@link OpenPgpV4Fingerprint}
* is listed in {@code encryptionKeys} is not available.
*/
OpenPgpElement signAndEncrypt(SigncryptElement element,
OpenPgpV4Fingerprint signingKey,
Set<OpenPgpV4Fingerprint> encryptionKeys)
throws MissingOpenPgpKeyPairException, MissingOpenPgpPublicKeyException;
byte[] signAndEncrypt(SigncryptElement element,
OpenPgpV4Fingerprint signingKey,
MultiMap<BareJid, OpenPgpV4Fingerprint> encryptionKeys)
throws MissingOpenPgpKeyPairException, MissingOpenPgpPublicKeyException, SmackOpenPgpException, IOException;
/**
* Decrypt an incoming {@link OpenPgpElement} which must contain a {@link SigncryptElement} and verify
* the signature made by the sender in the context of instant messaging.
*
* @see <a href="https://xmpp.org/extensions/xep-0374.html#openpgp-secured-im">XEP-0374 §2.1</a>
* @param element {@link OpenPgpElement} which contains an encrypted and signed {@link SigncryptElement}.
* @param sendersKeys {@link Set} of the senders {@link OpenPgpV4Fingerprint}s.
* It is required, that one of those keys was used for signing the message.
* @return decrypted {@link OpenPgpMessage} which contains the decrypted {@link SigncryptElement}.
* @throws MissingOpenPgpKeyPairException if we have no OpenPGP key pair to decrypt the message.
* @throws MissingOpenPgpPublicKeyException if we do not have the public OpenPGP key of the sender to
* verify the signature on the message.
*/
OpenPgpMessage decryptAndVerify(OpenPgpElement element, Set<OpenPgpV4Fingerprint> sendersKeys)
throws MissingOpenPgpKeyPairException, MissingOpenPgpPublicKeyException;
/**
* Sign a {@link SignElement} and pack it inside a {@link OpenPgpElement}.
* The resulting {@link OpenPgpElement} contains the {@link SignElement} signed and base64 encoded.
* <br>
* Note: DO NOT use this method in the context of instant messaging, as XEP-0374 forbids that.
* Sign a {@link SignElement} with th users signing key.
* The resulting byte array contains the signed byte representation of the {@link SignElement}.
*
* @see <a href="https://xmpp.org/extensions/xep-0373.html#exchange">XEP-0373 §3.1</a>
* @see <a href="https://xmpp.org/extensions/xep-0374.html#openpgp-secured-im">XEP-0374 §2.1</a>
*
* @param element {@link SignElement} which will be signed.
* @param singingKeyFingerprint {@link OpenPgpV4Fingerprint} of the key that is used for signing.
* @return {@link OpenPgpElement} which contains the signed, Base64 encoded {@link SignElement}.
* @return byte array which contains the signed {@link SignElement}.
*
* @throws MissingOpenPgpKeyPairException if we don't have the key pair for the
* {@link OpenPgpV4Fingerprint} available.
*/
OpenPgpElement sign(SignElement element, OpenPgpV4Fingerprint singingKeyFingerprint)
throws MissingOpenPgpKeyPairException;
byte[] sign(SignElement element, OpenPgpV4Fingerprint singingKeyFingerprint)
throws MissingOpenPgpKeyPairException, IOException, SmackOpenPgpException;
/**
* Verify the signature on an incoming {@link OpenPgpElement} which must contain a {@link SignElement}.
* <br>
* Note: DO NOT use this method in the context of instant messaging, as XEP-0374 forbids that.
*
* @see <a href="https://xmpp.org/extensions/xep-0373.html#exchange">XEP-0373 §3.1</a>
* @see <a href="https://xmpp.org/extensions/xep-0374.html#openpgp-secured-im">XEP-0374 §2.1</a>
* @param element incoming {@link OpenPgpElement} which must contain a signed {@link SignElement}.
* @param singingKeyFingerprints {@link Set} of the senders key {@link OpenPgpV4Fingerprint}s.
* It is required that one of those keys was used to sign
* the message.
* @return {@link OpenPgpMessage} which contains the decoded {@link SignElement}.
* @throws MissingOpenPgpPublicKeyException if we don't have the signers public key which signed
* the message available.
*/
OpenPgpMessage verify(OpenPgpElement element, Set<OpenPgpV4Fingerprint> singingKeyFingerprints)
throws MissingOpenPgpPublicKeyException;
/**
* Encrypt a {@link CryptElement} and pack it inside a {@link OpenPgpElement}.
* The resulting {@link OpenPgpElement} contains the encrypted and Base64 encoded {@link CryptElement}
* Encrypt a {@link CryptElement} for all keys which fingerprints are contained in
* {@code encryptionKeyFingerprints}.
* The resulting byte array contains the encrypted {@link CryptElement}
* which can be decrypted by all recipients, as well as by ourselves.
* <br>
* Note: DO NOT use this method in the context of instant messaging, as XEP-0374 forbids that.
*
* @see <a href="https://xmpp.org/extensions/xep-0374.html#openpgp-secured-im">XEP-0374 §2.1</a>
*
* @param element plaintext {@link CryptElement} which will be encrypted.
* @param encryptionKeyFingerprints {@link Set} of {@link OpenPgpV4Fingerprint}s of the keys which
* are used for encryption.
* @return {@link OpenPgpElement} which contains the encrypted, Base64 encoded {@link CryptElement}.
* @param encryptionKeyFingerprints {@link MultiMap} of recipients and {@link OpenPgpV4Fingerprint}s of the
* keys which are used for encryption.
* @return byte array which contains the encrypted {@link CryptElement}.
* @throws MissingOpenPgpPublicKeyException if any of the OpenPGP public keys whose
* {@link OpenPgpV4Fingerprint} is listed in {@code encryptionKeys}
* is not available.
*/
OpenPgpElement encrypt(CryptElement element, Set<OpenPgpV4Fingerprint> encryptionKeyFingerprints)
throws MissingOpenPgpPublicKeyException;
byte[] encrypt(CryptElement element, MultiMap<BareJid, OpenPgpV4Fingerprint> encryptionKeyFingerprints)
throws MissingOpenPgpPublicKeyException, IOException, SmackOpenPgpException;
/**
* Decrypt an incoming {@link OpenPgpElement} which must contain a {@link CryptElement}.
* The resulting {@link OpenPgpMessage} will contain the decrypted {@link CryptElement}.
* <br>
* Note: DO NOT use this method in the context of instant messaging, as XEP-0374 forbids that.
* Process an incoming {@link OpenPgpElement}.
* If its content is encrypted ({@link CryptElement} or {@link SigncryptElement}), the content will be decrypted.
* If its content is signed ({@link SignElement} or {@link SigncryptElement}), signatures are verified using
* the announced public keys of the sender.
* The resulting byte array will contain the decrypted {@link OpenPgpContentElement}.
*
* @see <a href="https://xmpp.org/extensions/xep-0373.html#exchange">XEP-0373 §3.1</a>
*
* @param bytes byte array which contains the encrypted {@link OpenPgpContentElement}.
* @return byte array which contains the decrypted {@link OpenPgpContentElement}, as well as metadata.
*
* @see <a href="https://xmpp.org/extensions/xep-0374.html#openpgp-secured-im">XEP-0374 §2.1</a>
* @param element {@link OpenPgpElement} which contains the encrypted {@link CryptElement}.
* @return {@link OpenPgpMessage} which contains the decrypted {@link CryptElement}.
* @throws MissingOpenPgpKeyPairException if we don't have an OpenPGP key pair available that to decrypt
* the message.
*/
OpenPgpMessage decrypt(OpenPgpElement element)
throws MissingOpenPgpKeyPairException;
DecryptedBytesAndMetadata decrypt(byte[] bytes, BareJid sender, SmackMissingOpenPgpPublicKeyCallback missingPublicKeyCallback)
throws MissingOpenPgpKeyPairException, SmackOpenPgpException, IOException;
byte[] symmetricallyEncryptWithPassword(byte[] bytes, String password) throws SmackOpenPgpException, IOException;
byte[] symmetricallyDecryptWithPassword(byte[] bytes, String password) throws SmackOpenPgpException, IOException;
/**
* Generate a fresh OpenPGP key pair.
*
* @param owner JID of the keys owner.
* @return byte array representation + {@link OpenPgpV4Fingerprint} of the generated key pair.
*/
KeyBytesAndFingerprint generateOpenPgpKeyPair(BareJid owner)
throws SmackOpenPgpException, InvalidAlgorithmParameterException, NoSuchAlgorithmException,
NoSuchProviderException, IOException;
OpenPgpV4Fingerprint importPublicKey(BareJid owner, byte[] bytes) throws MissingUserIdOnKeyException;
OpenPgpV4Fingerprint importSecretKey(BareJid owner, byte[] bytes) throws MissingUserIdOnKeyException;
OpenPgpStore getStore();
}

View file

@ -16,19 +16,11 @@
*/
package org.jivesoftware.smackx.ox;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.util.Date;
import java.util.Map;
import java.util.Set;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smackx.ox.callback.SecretKeyRestoreSelectionCallback;
import org.jivesoftware.smackx.ox.element.PubkeyElement;
import org.jivesoftware.smackx.ox.element.PublicKeysListElement;
import org.jivesoftware.smackx.ox.element.SecretkeyElement;
import org.jivesoftware.smackx.ox.element.SigncryptElement;
import org.jivesoftware.smackx.ox.exception.InvalidBackupCodeException;
import org.jivesoftware.smack.util.MultiMap;
import org.jivesoftware.smackx.ox.exception.MissingOpenPgpKeyPairException;
import org.jivesoftware.smackx.ox.exception.MissingOpenPgpPublicKeyException;
import org.jivesoftware.smackx.ox.exception.SmackOpenPgpException;
@ -45,123 +37,105 @@ public interface OpenPgpStore {
*
* @return fingerprint of the primary OpenPGP key pair.
*/
OpenPgpV4Fingerprint primaryOpenPgpKeyPairFingerprint();
OpenPgpV4Fingerprint getPrimaryOpenPgpKeyPairFingerprint();
/**
* Return a {@link Set} containing the {@link OpenPgpV4Fingerprint} of all available OpenPGP key pairs.
* Set the {@link OpenPgpV4Fingerprint} of the primary OpenPGP key pair.
* If multiple key pairs are available, only the primary key pair is used for signing.
*
* @return set of fingerprints of available OpenPGP key pairs.
* @param fingerprint {@link OpenPgpV4Fingerprint} of the new primary key pair.
*/
Set<OpenPgpV4Fingerprint> availableOpenPgpKeyPairFingerprints();
void setPrimaryOpenPgpKeyPairFingerprint(OpenPgpV4Fingerprint fingerprint);
/**
* Return a {@link Set} containing the {@link OpenPgpV4Fingerprint}s of the master keys of all available
* OpenPGP key pairs.
*
* @return set of fingerprints of available OpenPGP key pairs master keys.
*/
Set<OpenPgpV4Fingerprint> getAvailableKeyPairFingerprints();
/**
* Return a {@link Map} containing the {@link OpenPgpV4Fingerprint}s of all OpenPGP public keys of a
* contact, which we have locally available, as well as the date, those keys had been published on.
* <br>
* Note: This returns a {@link Map} that might be different from the result of
* {@link #getAvailableKeysFingerprints(BareJid)} (BareJid)}.
* Messages should be encrypted to the intersection of both key sets.
*
* @param contact contact.
* @return list of contacts locally available public keys.
*
* @throws SmackOpenPgpException if something goes wrong
*/
Map<OpenPgpV4Fingerprint, Date> getAvailableKeysFingerprints(BareJid contact)
throws SmackOpenPgpException;
/**
* Return a {@link Map} containing the {@link OpenPgpV4Fingerprint}s of all currently announced OpenPGP
* public keys of a contact along with the dates of their latest update.
* public keys of a contact along with the dates of their latest revision.
* <br>
* Note: Those are the keys announced in the latest received metadata update.
* This returns a {@link Map} which might contain different {@link OpenPgpV4Fingerprint}s than the result of
* {@link #availableOpenPgpPublicKeysFingerprints(BareJid)}.
* Messages should be encrypted to the intersection of both sets.
* {@link #getAvailableKeysFingerprints(BareJid)} (BareJid)}.
* Messages should be encrypted to the intersection of both key sets.
*
* @param contact contact.
* @return map of contacts last announced public keys and their update dates.
*/
Map<OpenPgpV4Fingerprint, Date> announcedOpenPgpKeyFingerprints(BareJid contact);
Map<OpenPgpV4Fingerprint, Date> getAnnouncedKeysFingerprints(BareJid contact);
/**
* Return a {@link Set} containing the {@link OpenPgpV4Fingerprint}s of all OpenPGP public keys of a
* contact, which we have locally available.
* <br>
* Note: This returns a {@link Set} that might be different from the result of
* {@link #availableOpenPgpPublicKeysFingerprints(BareJid)}.
* Messages should be encrypted to the intersection of both sets.
* Store a {@Map} of a contacts fingerprints and publication dates in persistent storage.
*
* @param contact contact.
* @return list of contacts locally available public keys.
* @throws SmackOpenPgpException if something goes wrong
* @param contact {@link BareJid} of the owner of the announced public keys.
* @param fingerprints {@link Map} which contains a list of the keys of {@code owner}.
*/
Set<OpenPgpV4Fingerprint> availableOpenPgpPublicKeysFingerprints(BareJid contact)
throws SmackOpenPgpException;
void setAnnouncedKeysFingerprints(BareJid contact, Map<OpenPgpV4Fingerprint, Date> fingerprints);
/**
* Store incoming update to the OpenPGP metadata node in persistent storage.
*
* @param connection authenticated {@link XMPPConnection} of the user.
* @param listElement {@link PublicKeysListElement} which contains a list of the keys of {@code owner}.
* @param owner {@link BareJid} of the owner of the announced public keys.
*/
void storePublicKeysList(XMPPConnection connection, PublicKeysListElement listElement, BareJid owner);
/**
* Create a fresh OpenPGP key pair with the {@link BareJid} of the user prefixed by "xmpp:" as user-id
* (example: {@code "xmpp:juliet@capulet.lit"}).
* Store the key pair in persistent storage and return the public keys {@link OpenPgpV4Fingerprint}.
*
* @return {@link OpenPgpV4Fingerprint} of the generated key pair.
* @throws NoSuchAlgorithmException if a Hash algorithm is not available
* @throws NoSuchProviderException id no suitable cryptographic provider (for example BouncyCastleProvider)
* is registered.
* @throws SmackOpenPgpException if the generated key cannot be added to the keyring for some reason.
*/
OpenPgpV4Fingerprint createOpenPgpKeyPair()
throws NoSuchAlgorithmException, NoSuchProviderException, SmackOpenPgpException;
/**
* Create a {@link PubkeyElement} which contains our exported OpenPGP public key.
* The element can for example be published.
*
* @return {@link PubkeyElement} containing our public key.
* @throws MissingOpenPgpPublicKeyException if we have no OpenPGP key pair.
* @throws SmackOpenPgpException if something goes wrong.
*/
PubkeyElement createPubkeyElement(OpenPgpV4Fingerprint fingerprint)
throws SmackOpenPgpException, MissingOpenPgpPublicKeyException;
/**
* Process an incoming {@link PubkeyElement} of a contact or ourselves.
* That typically includes importing/updating the key.
*
* @param owner owner of the OpenPGP public key contained in the {@link PubkeyElement}.
* @param fingerprint {@link OpenPgpV4Fingerprint} of the key.
* @param element {@link PubkeyElement} which presumably contains the public key of the {@code owner}.
* @param currentMetadataDate {@link Date} which is currently found in the metadata node for this key.
* @throws SmackOpenPgpException if the key found in the {@link PubkeyElement}
* can not be deserialized or imported.
*/
void storePublicKey(BareJid owner, OpenPgpV4Fingerprint fingerprint, PubkeyElement element, Date currentMetadataDate)
throws SmackOpenPgpException;
/**
* Return the {@link Date} of the last time on which the key has been fetched from PubSub.
* Return the {@link Date} of the last revision which was fetched from PubSub.
*
* @param owner owner of the key
* @param fingerprint fingerprint of the key.
* @return {@link Date} or {@code null} if no record found.
*/
Date getPubkeysLatestUpdateDate(BareJid owner, OpenPgpV4Fingerprint fingerprint);
Date getPubkeysLastRevision(BareJid owner, OpenPgpV4Fingerprint fingerprint);
/**
* Create an encrypted backup of our secret keys.
* Set the {@link Date} of the last revision which was fetched from PubSub.
*
* @param fingerprints {@link Set} of IDs of the keys that will be included in the backup.
* @param password password that is used to symmetrically encrypt the backup.
* @return {@link SigncryptElement} containing the selected encrypted secret keys.
* @throws MissingOpenPgpKeyPairException if we don't have an OpenPGP key available.
* @throws SmackOpenPgpException if for some reason the key pair cannot be serialized.
* @param owner owner of the key
* @param fingerprint fingerprint of the key
* @param revision {@link Date} of the revision
*/
SecretkeyElement createSecretkeyElement(Set<OpenPgpV4Fingerprint> fingerprints, String password)
throws MissingOpenPgpKeyPairException, SmackOpenPgpException;
void setPubkeysLastRevision(BareJid owner, OpenPgpV4Fingerprint fingerprint, Date revision);
/**
* Decrypt a secret key backup and restore the key from it.
* Return a {@link MultiMap} which contains contacts and their trusted keys {@link OpenPgpV4Fingerprint}s.
*
* @param secretkeyElement {@link SecretkeyElement} containing the backup.
* @param password password to decrypt the backup.
* @param callback {@link SecretKeyRestoreSelectionCallback} to let the user decide which key to restore.
* @throws SmackOpenPgpException if the selected key is corrupted and cannot be restored or our key ring
* is corrupted.
* @throws InvalidBackupCodeException if the user provided backup code is invalid.
* @return trusted fingerprints.
*/
void restoreSecretKeyBackup(SecretkeyElement secretkeyElement, String password, SecretKeyRestoreSelectionCallback callback)
throws SmackOpenPgpException, InvalidBackupCodeException;
MultiMap<BareJid, OpenPgpV4Fingerprint> getAllContactsTrustedFingerprints();
/**
* Return the byte array representation of {@code owner}s public key ring with fingerprint {@code fingerprint}.
*
* @param owner owner of the key
* @param fingerprint fingerprint of the key
* @return byte representation of the public key.
*/
byte[] getPublicKeyBytes(BareJid owner, OpenPgpV4Fingerprint fingerprint)
throws MissingOpenPgpPublicKeyException;
/**
* Return the byte array representation of {@code owner}s secret key ring with fingerprint {@code fingerprint}.
*
* @param owner owner of the key
* @param fingerprint fingerprint of the key
* @return byte representation of the secret key.
*/
byte[] getSecretKeyBytes(BareJid owner, OpenPgpV4Fingerprint fingerprint)
throws MissingOpenPgpKeyPairException;
}

View file

@ -19,6 +19,7 @@ package org.jivesoftware.smackx.ox;
import java.nio.charset.Charset;
import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smackx.ox.util.Util;
/**
* This class represents an hex encoded, uppercase OpenPGP v4 fingerprint.

View file

@ -0,0 +1,4 @@
package org.jivesoftware.smackx.ox.callback;
public interface SmackMissingOpenPgpPublicKeyCallback {
}

View file

@ -14,25 +14,30 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.ox;
package org.jivesoftware.smackx.ox.chat;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.chat2.Chat;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.util.MultiMap;
import org.jivesoftware.smack.util.stringencoder.Base64;
import org.jivesoftware.smackx.eme.element.ExplicitMessageEncryptionElement;
import org.jivesoftware.smackx.hints.element.StoreHint;
import org.jivesoftware.smackx.ox.OpenPgpProvider;
import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint;
import org.jivesoftware.smackx.ox.element.OpenPgpElement;
import org.jivesoftware.smackx.ox.element.SigncryptElement;
import org.jivesoftware.smackx.ox.exception.MissingOpenPgpKeyPairException;
import org.jivesoftware.smackx.ox.exception.MissingOpenPgpPublicKeyException;
import org.jivesoftware.smackx.ox.exception.SmackOpenPgpException;
import org.jxmpp.jid.BareJid;
import org.jxmpp.jid.Jid;
public class OpenPgpEncryptedChat {
@ -49,31 +54,35 @@ public class OpenPgpEncryptedChat {
OpenPgpFingerprints contactsFingerprints) {
this.cryptoProvider = cryptoProvider;
this.chat = chat;
this.singingKey = cryptoProvider.primaryOpenPgpKeyPairFingerprint();
this.singingKey = cryptoProvider.getStore().getPrimaryOpenPgpKeyPairFingerprint();
this.ourFingerprints = ourFingerprints;
this.contactsFingerprints = contactsFingerprints;
}
public void send(Message message, List<ExtensionElement> payload)
throws MissingOpenPgpKeyPairException, SmackException.NotConnectedException, InterruptedException {
Set<OpenPgpV4Fingerprint> encryptionFingerprints = new HashSet<>(contactsFingerprints.getActiveKeys());
encryptionFingerprints.addAll(ourFingerprints.getActiveKeys());
throws MissingOpenPgpKeyPairException, SmackException.NotConnectedException, InterruptedException,
SmackOpenPgpException, IOException {
MultiMap<BareJid, OpenPgpV4Fingerprint> fingerprints = oursAndRecipientFingerprints();
SigncryptElement preparedPayload = new SigncryptElement(
Collections.<Jid>singleton(chat.getXmppAddressOfChatPartner()),
payload);
OpenPgpElement encryptedPayload;
byte[] encryptedMessage;
// Encrypt the payload
try {
encryptedPayload = cryptoProvider.signAndEncrypt(
encryptedMessage = cryptoProvider.signAndEncrypt(
preparedPayload,
singingKey,
encryptionFingerprints);
fingerprints);
} catch (MissingOpenPgpPublicKeyException e) {
throw new AssertionError("Missing OpenPGP public key, even though this should not happen here.", e);
}
encryptedPayload = new OpenPgpElement(Base64.encodeToString(encryptedMessage));
// Add encrypted payload to message
message.addExtension(encryptedPayload);
@ -87,9 +96,25 @@ public class OpenPgpEncryptedChat {
}
public void send(Message message, CharSequence body)
throws MissingOpenPgpKeyPairException, SmackException.NotConnectedException, InterruptedException {
throws MissingOpenPgpKeyPairException, SmackException.NotConnectedException, InterruptedException,
SmackOpenPgpException, IOException {
List<ExtensionElement> payload = new ArrayList<>();
payload.add(new Message.Body(null, body.toString()));
send(message, payload);
}
private MultiMap<BareJid, OpenPgpV4Fingerprint> oursAndRecipientFingerprints() {
MultiMap<BareJid, OpenPgpV4Fingerprint> fingerprints = new MultiMap<>();
for (OpenPgpV4Fingerprint f : contactsFingerprints.getActiveKeys()) {
fingerprints.put(contactsFingerprints.getJid(), f);
}
if (!contactsFingerprints.getJid().equals(ourFingerprints.getJid())) {
for (OpenPgpV4Fingerprint f : ourFingerprints.getActiveKeys()) {
fingerprints.put(ourFingerprints.getJid(), f);
}
}
return fingerprints;
}
}

View file

@ -14,13 +14,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.ox;
package org.jivesoftware.smackx.ox.chat;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint;
import org.jxmpp.jid.BareJid;
/**
@ -28,6 +30,7 @@ import org.jxmpp.jid.BareJid;
*/
public class OpenPgpFingerprints {
private final BareJid jid;
private final Set<OpenPgpV4Fingerprint> announcedKeys;
private final Set<OpenPgpV4Fingerprint> availableKeys;
private final Map<OpenPgpV4Fingerprint, Throwable> unfetchableKeys;
@ -39,14 +42,24 @@ public class OpenPgpFingerprints {
* @param availableKeys keys which contain the contacts {@link BareJid} as user ID.
* @param unfetchableKeys keys that are announced, but cannot be fetched fro PubSub.
*/
public OpenPgpFingerprints(Set<OpenPgpV4Fingerprint> announcedKeys,
public OpenPgpFingerprints(BareJid jid,
Set<OpenPgpV4Fingerprint> announcedKeys,
Set<OpenPgpV4Fingerprint> availableKeys,
Map<OpenPgpV4Fingerprint, Throwable> unfetchableKeys) {
this.jid = jid;
this.announcedKeys = Collections.unmodifiableSet(announcedKeys);
this.availableKeys = Collections.unmodifiableSet(availableKeys);
this.unfetchableKeys = Collections.unmodifiableMap(unfetchableKeys);
}
/**
* Return the {@link BareJid} of the user.
* @return jid
*/
public BareJid getJid() {
return jid;
}
/**
* Return a {@link Set} of {@link OpenPgpV4Fingerprint}s, which the contact in question announced via their
* metadata node.

View file

@ -14,10 +14,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.ox;
package org.jivesoftware.smackx.ox.chat;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.HashSet;
import java.util.Set;
import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.util.stringencoder.Base64;
import org.jivesoftware.smackx.ox.element.CryptElement;
import org.jivesoftware.smackx.ox.element.OpenPgpContentElement;
import org.jivesoftware.smackx.ox.element.OpenPgpElement;
@ -49,7 +54,7 @@ public class OpenPgpMessage {
}
private final String element;
private State state;
private final State state;
private OpenPgpContentElement openPgpContentElement;
@ -60,8 +65,13 @@ public class OpenPgpMessage {
* @param content XML representation of the decrypted {@link OpenPgpContentElement}.
*/
public OpenPgpMessage(State state, String content) {
this.state = state;
this.element = content;
this.state = Objects.requireNonNull(state);
this.element = Objects.requireNonNull(content);
}
public OpenPgpMessage(byte[] bytes, Metadata metadata) {
this.element = new String(Base64.decode(bytes), Charset.forName("UTF-8"));
this.state = metadata.getState();
}
/**
@ -90,14 +100,17 @@ public class OpenPgpMessage {
// Determine the state of the content element.
if (openPgpContentElement instanceof SigncryptElement) {
state = State.signcrypt;
if (state != State.signcrypt) {
throw new IllegalStateException("OpenPgpContentElement was signed and encrypted, but is not a SigncryptElement.");
}
} else if (openPgpContentElement instanceof SignElement) {
state = State.sign;
if (state != State.sign) {
throw new IllegalStateException("OpenPgpContentElement was signed and unencrypted, but is not a SignElement.");
}
} else if (openPgpContentElement instanceof CryptElement) {
state = State.crypt;
} else {
throw new AssertionError("OpenPgpContentElement is neither a SignElement, " +
"CryptElement nor a SignCryptElement.");
if (state != State.crypt) {
throw new IllegalStateException("OpenPgpContentElement was unsigned and encrypted, but is not a CryptElement.");
}
}
}
@ -113,4 +126,40 @@ public class OpenPgpMessage {
ensureOpenPgpContentElementSet();
return state;
}
public static class Metadata {
private final Long encryptionKeyId;
private final Set<Long> validSignatureIds;
public Metadata(Long encryptionKeyId, Set<Long> validSignatureIds) {
this.encryptionKeyId = encryptionKeyId;
this.validSignatureIds = validSignatureIds;
}
public Long getEncryptionKeyId() {
return encryptionKeyId;
}
public Set<Long> getValidSignatureIds() {
return new HashSet<>(validSignatureIds);
}
public State getState() {
if (validSignatureIds.size() != 0) {
if (encryptionKeyId != null) {
return State.signcrypt;
} else {
return State.sign;
}
} else {
if (encryptionKeyId != null) {
return State.crypt;
} else {
throw new IllegalStateException("OpenPGP message appears to be neither encrypted, " +
"nor signed.");
}
}
}
}
}

View file

@ -31,8 +31,9 @@ public class MissingOpenPgpKeyPairException extends Exception {
* Create a new {@link MissingOpenPgpKeyPairException}.
*
* @param owner owner of the missing key pair.
* @param e
*/
public MissingOpenPgpKeyPairException(BareJid owner) {
public MissingOpenPgpKeyPairException(BareJid owner, Throwable e) {
super("Missing OpenPGP key pair for user " + owner);
this.owner = owner;
}

View file

@ -27,8 +27,8 @@ public class MissingOpenPgpPublicKeyException extends Exception {
private static final long serialVersionUID = 1L;
private final BareJid user;
private final OpenPgpV4Fingerprint fingerprint;
private BareJid user;
private OpenPgpV4Fingerprint fingerprint;
/**
* Create a new {@link MissingOpenPgpPublicKeyException}.
@ -42,6 +42,10 @@ public class MissingOpenPgpPublicKeyException extends Exception {
this.fingerprint = fingerprint;
}
public MissingOpenPgpPublicKeyException(Throwable e) {
}
/**
* Return the {@link BareJid} of the owner of the missing key.
*

View file

@ -0,0 +1,12 @@
package org.jivesoftware.smackx.ox.exception;
import org.jxmpp.jid.BareJid;
public class MissingUserIdOnKeyException extends Exception {
private static final long serialVersionUID = 1L;
public MissingUserIdOnKeyException(BareJid owner, long keyId) {
super("Key " + Long.toHexString(keyId) + " does not have a user-id of \"xmpp:" + owner.toString() + "\".");
}
}

View file

@ -0,0 +1,4 @@
package org.jivesoftware.smackx.ox.exception;
public class NoBackupFoundException {
}

View file

@ -17,7 +17,7 @@
package org.jivesoftware.smackx.ox.listener;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smackx.ox.OpenPgpEncryptedChat;
import org.jivesoftware.smackx.ox.chat.OpenPgpEncryptedChat;
import org.jivesoftware.smackx.ox.element.OpenPgpElement;
import org.jivesoftware.smackx.ox.element.SigncryptElement;
@ -36,8 +36,8 @@ public interface OpenPgpEncryptedMessageListener {
* @param decryptedPayload decrypted {@link SigncryptElement} which is carrying the payload.
* @param chat {@link OpenPgpEncryptedChat} which is the context of the message.
*/
void newIncomingEncryptedMessage(EntityBareJid from,
Message originalMessage,
SigncryptElement decryptedPayload,
OpenPgpEncryptedChat chat);
void newIncomingOxMessage(EntityBareJid from,
Message originalMessage,
SigncryptElement decryptedPayload,
OpenPgpEncryptedChat chat);
}

View file

@ -0,0 +1,30 @@
package org.jivesoftware.smackx.ox.util;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
public class DecryptedBytesAndMetadata {
private final byte[] bytes;
private final Set<Long> verifiedSignatures;
private final Long decryptionKey;
public DecryptedBytesAndMetadata(byte[] bytes, Set<Long> verifiedSignatures, Long decryptionKey) {
this.bytes = bytes;
this.verifiedSignatures = verifiedSignatures;
this.decryptionKey = decryptionKey;
}
public byte[] getBytes() {
return Arrays.copyOf(bytes, bytes.length);
}
public Long getDecryptionKey() {
return decryptionKey;
}
public Set<Long> getVerifiedSignatures() {
return new HashSet<>(verifiedSignatures);
}
}

View file

@ -0,0 +1,24 @@
package org.jivesoftware.smackx.ox.util;
import java.util.Arrays;
import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint;
public class KeyBytesAndFingerprint {
private final byte[] bytes;
private final OpenPgpV4Fingerprint fingerprint;
public KeyBytesAndFingerprint(byte[] bytes, OpenPgpV4Fingerprint fingerprint) {
this.bytes = bytes;
this.fingerprint = fingerprint;
}
public byte[] getBytes() {
return Arrays.copyOf(bytes, bytes.length);
}
public OpenPgpV4Fingerprint getFingerprint() {
return fingerprint;
}
}

View file

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.ox;
package org.jivesoftware.smackx.ox.util;
import org.jivesoftware.smack.initializer.UrlInitializer;

View file

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.ox;
package org.jivesoftware.smackx.ox.util;
import java.util.Date;
import java.util.List;
@ -25,6 +25,7 @@ import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint;
import org.jivesoftware.smackx.ox.element.PubkeyElement;
import org.jivesoftware.smackx.ox.element.PublicKeysListElement;
import org.jivesoftware.smackx.ox.element.SecretkeyElement;
@ -72,7 +73,19 @@ public class PubSubDelegate {
return PEP_NODE_PUBLIC_KEYS + ":" + id;
}
/**
* Query the access model of {@code node}. If it is different from {@code accessModel}, change the access model
* of the node to {@code accessModel}.
*
* @see <a href="https://xmpp.org/extensions/xep-0060.html#accessmodels">XEP-0060 §4.5 - Node Access Models</a>
*
* @param node {@link LeafNode} whose PubSub access model we want to change
* @param accessModel new access model.
* @throws XMPPException.XMPPErrorException
* @throws SmackException.NotConnectedException
* @throws InterruptedException
* @throws SmackException.NoResponseException
*/
public static void changeAccessModelIfNecessary(LeafNode node, AccessModel accessModel)
throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
SmackException.NoResponseException {

View file

@ -14,12 +14,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.ox;
package org.jivesoftware.smackx.ox.util;
import java.nio.ByteBuffer;
import java.util.Arrays;
import javax.xml.bind.DatatypeConverter;
import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint;
public class Util {
/**