From 55b9c1ac2a4472f430afaa553ce3c6a1f110beca Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 22 May 2018 13:54:53 +0200 Subject: [PATCH] Modify API Add methods to decrypt messages Add lots of high quality documentation --- .../BouncyCastleOpenPgpProvider.java | 113 ++++++++++++++--- .../BouncyCastleOpenPgpProviderTest.java | 2 +- .../smackx/ox/OpenPgpMessage.java | 8 +- .../smackx/ox/OpenPgpMessageListener.java | 19 +++ .../smackx/ox/OpenPgpProvider.java | 114 +++++++++++++++++- 5 files changed, 234 insertions(+), 22 deletions(-) diff --git a/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/BouncyCastleOpenPgpProvider.java b/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/BouncyCastleOpenPgpProvider.java index 931fe199b..7ff829069 100644 --- a/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/BouncyCastleOpenPgpProvider.java +++ b/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/BouncyCastleOpenPgpProvider.java @@ -31,9 +31,12 @@ import java.util.Set; import org.jivesoftware.smack.util.stringencoder.Base64; import org.jivesoftware.smackx.ox.OpenPgpMessage; import org.jivesoftware.smackx.ox.OpenPgpProvider; +import org.jivesoftware.smackx.ox.element.CryptElement; import org.jivesoftware.smackx.ox.element.OpenPgpElement; import org.jivesoftware.smackx.ox.element.PubkeyElement; import org.jivesoftware.smackx.ox.element.PublicKeysListElement; +import org.jivesoftware.smackx.ox.element.SignElement; +import org.jivesoftware.smackx.ox.element.SigncryptElement; import org.jivesoftware.smackx.ox.exception.CorruptedOpenPgpKeyException; import name.neuhalfen.projects.crypto.bouncycastle.openpgp.BouncyGPG; @@ -81,14 +84,14 @@ public class BouncyCastleOpenPgpProvider implements OpenPgpProvider { } @Override - public void processPubkeyElement(PubkeyElement element, BareJid jid) throws CorruptedOpenPgpKeyException { + public void processPubkeyElement(PubkeyElement element, BareJid owner) throws CorruptedOpenPgpKeyException { byte[] decoded = Base64.decode(element.getDataElement().getB64Data()); try { - InMemoryKeyring contactsKeyring = theirKeys.get(jid); + InMemoryKeyring contactsKeyring = theirKeys.get(owner); if (contactsKeyring == null) { contactsKeyring = KeyringConfigs.forGpgExportedKeys(KeyringConfigCallbacks.withUnprotectedKeys()); - theirKeys.put(jid, contactsKeyring); + theirKeys.put(owner, contactsKeyring); } contactsKeyring.addPublicKey(decoded); @@ -98,12 +101,12 @@ public class BouncyCastleOpenPgpProvider implements OpenPgpProvider { } @Override - public void processPublicKeysListElement(PublicKeysListElement listElement, BareJid from) throws Exception { + public void processPublicKeysListElement(PublicKeysListElement listElement, BareJid owner) throws Exception { } @Override - public OpenPgpElement signAndEncrypt(InputStream inputStream, Set recipients) + public OpenPgpElement signAndEncrypt(SigncryptElement element, Set recipients) throws Exception { if (recipients.isEmpty()) { throw new IllegalArgumentException("Set of recipients must not be empty"); @@ -119,7 +122,7 @@ public class BouncyCastleOpenPgpProvider implements OpenPgpProvider { } } - // Add our keys to encryption config + // Add our public and secret keys to encryption config for (PGPPublicKeyRing p : ourKeys.getPublicKeyRings()) { encryptionConfig.addPublicKey(p.getPublicKey().getEncoded()); } @@ -134,6 +137,7 @@ public class BouncyCastleOpenPgpProvider implements OpenPgpProvider { } recipientUIDs[pos] = "xmpp:" + ourJid.toString(); + InputStream inputStream = element.toInputStream(); ByteArrayOutputStream encryptedOut = new ByteArrayOutputStream(); OutputStream encryptor = BouncyGPG.encryptToStream() @@ -154,28 +158,109 @@ public class BouncyCastleOpenPgpProvider implements OpenPgpProvider { } @Override - public OpenPgpElement sign(InputStream inputStream) throws Exception { + public OpenPgpElement sign(SignElement element) throws Exception { + InMemoryKeyring signingConfig = KeyringConfigs.forGpgExportedKeys(KeyringConfigCallbacks.withUnprotectedKeys()); + + // Add our secret keys to signing config + for (PGPSecretKeyRing s : ourKeys.getSecretKeyRings()) { + signingConfig.addSecretKey(s.getSecretKey().getEncoded()); + } + + InputStream inputStream = element.toInputStream(); + // TODO: Implement + return null; } @Override - public OpenPgpElement encrypt(InputStream inputStream, Set recipients) throws Exception { + public OpenPgpMessage verify(OpenPgpElement element, BareJid sender) throws Exception { + // TODO: Implement return null; } + @Override + public OpenPgpMessage decrypt(OpenPgpElement element) throws Exception { + InMemoryKeyring decryptionConfig = KeyringConfigs.forGpgExportedKeys(KeyringConfigCallbacks.withUnprotectedKeys()); + + // Add our secret keys to decryption config + for (PGPSecretKeyRing s : ourKeys.getSecretKeyRings()) { + decryptionConfig.addSecretKey(s.getSecretKey().getEncoded()); + } + + ByteArrayInputStream encryptedIn = new ByteArrayInputStream( + element.getEncryptedBase64MessageContent().getBytes(Charset.forName("UTF-8"))); + + InputStream decrypted = BouncyGPG.decryptAndVerifyStream() + .withConfig(decryptionConfig) + .withKeySelectionStrategy(new XmppKeySelectionStrategy(new Date())) + .andIgnoreSignatures() + .fromEncryptedInputStream(encryptedIn); + + ByteArrayOutputStream decryptedOut = new ByteArrayOutputStream(); + + Streams.pipeAll(decrypted, decryptedOut); + + return new OpenPgpMessage(OpenPgpMessage.State.crypt, new String(decryptedOut.toByteArray(), Charset.forName("UTF-8"))); + } + + @Override + public OpenPgpElement encrypt(CryptElement element, Set recipients) throws Exception { + if (recipients.isEmpty()) { + throw new IllegalArgumentException("Set of recipients must not be empty"); + } + + InMemoryKeyring encryptionConfig = KeyringConfigs.forGpgExportedKeys(KeyringConfigCallbacks.withUnprotectedKeys()); + + // Add all recipients public keys to encryption config + for (BareJid recipient : recipients) { + KeyringConfig c = theirKeys.get(recipient); + for (PGPPublicKeyRing p : c.getPublicKeyRings()) { + encryptionConfig.addPublicKey(p.getPublicKey().getEncoded()); + } + } + + // Add our public keys to encryption config + for (PGPPublicKeyRing p : ourKeys.getPublicKeyRings()) { + encryptionConfig.addPublicKey(p.getPublicKey().getEncoded()); + } + + String[] recipientUIDs = new String[recipients.size() + 1]; + int pos = 0; + for (BareJid b : recipients) { + recipientUIDs[pos++] = "xmpp:" + b.toString(); + } + recipientUIDs[pos] = "xmpp:" + ourJid.toString(); + + InputStream inputStream = element.toInputStream(); + ByteArrayOutputStream encryptedOut = new ByteArrayOutputStream(); + + OutputStream encryptor = BouncyGPG.encryptToStream() + .withConfig(encryptionConfig) + .withKeySelectionStrategy(new XmppKeySelectionStrategy(new Date())) + .withOxAlgorithms() + .toRecipients(recipientUIDs) + .andDoNotSign() + .binaryOutput() + .andWriteTo(encryptedOut); + + Streams.pipeAll(inputStream, encryptor); + encryptor.close(); + + String base64 = Base64.encodeToString(encryptedOut.toByteArray()); + + return new OpenPgpElement(base64); + } + @Override public OpenPgpMessage decryptAndVerify(OpenPgpElement element, BareJid sender) throws Exception { InMemoryKeyring decryptionConfig = KeyringConfigs.forGpgExportedKeys(KeyringConfigCallbacks.withUnprotectedKeys()); - // Add our keys to decryption config - // for (PGPPublicKeyRing p : ourKeys.getPublicKeyRings()) { - // decryptionConfig.addPublicKey(p.getPublicKey().getEncoded()); - // } + // Add our secret keys to decryption config for (PGPSecretKeyRing s : ourKeys.getSecretKeyRings()) { decryptionConfig.addSecretKey(s.getSecretKey().getEncoded()); } - // Add their keys to decryption config + // Add their public keys to decryption config for (PGPPublicKeyRing p : theirKeys.get(sender).getPublicKeyRings()) { decryptionConfig.addPublicKey(p.getPublicKey().getEncoded()); } @@ -193,7 +278,7 @@ public class BouncyCastleOpenPgpProvider implements OpenPgpProvider { Streams.pipeAll(decrypted, decryptedOut); - return new OpenPgpMessage(new String(decryptedOut.toByteArray(), Charset.forName("UTF-8"))); + return new OpenPgpMessage(OpenPgpMessage.State.signcrypt, new String(decryptedOut.toByteArray(), Charset.forName("UTF-8"))); } @Override diff --git a/smack-openpgp-bouncycastle/src/test/java/org/jivesoftware/smackx/ox/bouncycastle/BouncyCastleOpenPgpProviderTest.java b/smack-openpgp-bouncycastle/src/test/java/org/jivesoftware/smackx/ox/bouncycastle/BouncyCastleOpenPgpProviderTest.java index db36d8478..ecb4fe52e 100644 --- a/smack-openpgp-bouncycastle/src/test/java/org/jivesoftware/smackx/ox/bouncycastle/BouncyCastleOpenPgpProviderTest.java +++ b/smack-openpgp-bouncycastle/src/test/java/org/jivesoftware/smackx/ox/bouncycastle/BouncyCastleOpenPgpProviderTest.java @@ -61,7 +61,7 @@ public class BouncyCastleOpenPgpProviderTest extends SmackTestSuite { Collections.singletonList( new Message.Body("en", "How do you know I’m mad?"))); OpenPgpElement encrypted = aliceProvider.signAndEncrypt( - signcryptElement.toInputStream(), + signcryptElement, Collections.singleton(cheshire)); // Decrypt the message as the cheshire cat diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpMessage.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpMessage.java index a891371cb..6f312ebfc 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpMessage.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpMessage.java @@ -39,7 +39,8 @@ public class OpenPgpMessage { private OpenPgpContentElement openPgpContentElement; - public OpenPgpMessage(String content) { + public OpenPgpMessage(State state, String content) { + this.state = state; this.element = content; } @@ -66,4 +67,9 @@ public class OpenPgpMessage { state = State.crypt; } } + + public State getState() throws IOException, XmlPullParserException { + ensureOpenPgpContentElementSet(); + return state; + } } diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpMessageListener.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpMessageListener.java index ffb4d99d7..c389f3b2f 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpMessageListener.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpMessageListener.java @@ -23,9 +23,28 @@ import org.jivesoftware.smackx.ox.element.SigncryptElement; import org.jxmpp.jid.BareJid; public interface OpenPgpMessageListener { + + /** + * This method gets called whenever we received and successfully decrypted/verified an encrypted, signed message. + * + * @param from sender/signer of the message. + * @param signcryptElement decrypted and verified {@link SigncryptElement}. + */ void signcryptElementReceived(BareJid from, SigncryptElement signcryptElement); + /** + * This method gets called whenever we received and successfully verified a signed message. + * + * @param from sender/signer of the message. + * @param signElement verified {@link SignElement}. + */ void signElementReceived(BareJid from, SignElement signElement); + /** + * This method gets called whenever we received and successfully decrypted an encrypted message. + * + * @param from sender of the message. + * @param cryptElement decrypted {@link CryptElement}. + */ void cryptElementReceived(BareJid from, CryptElement cryptElement); } diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpProvider.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpProvider.java index 9cc801c08..e2f50d1e8 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpProvider.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpProvider.java @@ -16,31 +16,133 @@ */ package org.jivesoftware.smackx.ox; -import java.io.InputStream; import java.util.Set; +import org.jivesoftware.smackx.ox.element.CryptElement; import org.jivesoftware.smackx.ox.element.OpenPgpElement; import org.jivesoftware.smackx.ox.element.PubkeyElement; import org.jivesoftware.smackx.ox.element.PublicKeysListElement; +import org.jivesoftware.smackx.ox.element.SignElement; +import org.jivesoftware.smackx.ox.element.SigncryptElement; import org.jivesoftware.smackx.ox.exception.CorruptedOpenPgpKeyException; 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 message contains a signature made by our key. + * + * @see XEP-0373 §3 + * @see XEP-0374 §2.1 + * @param element {@link SigncryptElement} which contains the content of the message as plaintext. + * @param recipients {@link Set} of {@link BareJid} of recipients. + * @return encrypted {@link OpenPgpElement} which contains the encrypted, encoded message. + * @throws Exception + */ + OpenPgpElement signAndEncrypt(SigncryptElement element, Set recipients) throws Exception; + + /** + * 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 XEP-0374 §2.1 + * @param element {@link OpenPgpElement} which contains an encrypted and signed {@link SigncryptElement}. + * @param sender {@link BareJid} of the user which sent the message. This is also the user who signed the message. + * @return decrypted {@link OpenPgpMessage} which contains the decrypted {@link SigncryptElement}. + * @throws Exception + */ OpenPgpMessage decryptAndVerify(OpenPgpElement element, BareJid sender) throws Exception; - OpenPgpElement signAndEncrypt(InputStream inputStream, Set recipients) throws Exception; + /** + * Sign a {@link SignElement} and pack it inside a {@link OpenPgpElement}. + * The resulting {@link OpenPgpElement} contains the {@link SignElement} signed and base64 encoded. + * + * Note: DO NOT use this method in the context of instant messaging, as XEP-0374 forbids that. + * + * @see XEP-0373 §3.1 + * @see XEP-0374 §2.1 + * @param element {@link SignElement} which will be signed. + * @return {@link OpenPgpElement} which contains the signed, Base64 encoded {@link SignElement}. + * @throws Exception + */ + OpenPgpElement sign(SignElement element) throws Exception; - OpenPgpElement sign(InputStream inputStream) throws Exception; + /** + * Verify the signature on an incoming {@link OpenPgpElement} which must contain a {@link SignElement}. + * + * Note: DO NOT use this method in the context of instant messaging, as XEP-0374 forbids that. + * + * @see XEP-0373 §3.1 + * @see XEP-0374 §2.1 + * @param element incoming {@link OpenPgpElement} which must contain a signed {@link SignElement}. + * @param sender {@link BareJid} of the sender which also signed the message. + * @return {@link OpenPgpMessage} which contains the decoded {@link SignElement}. + * @throws Exception + */ + OpenPgpMessage verify(OpenPgpElement element, BareJid sender) throws Exception; - OpenPgpElement encrypt(InputStream inputStream, Set recipients) throws Exception; + /** + * Encrypt a {@link CryptElement} and pack it inside a {@link OpenPgpElement}. + * The resulting {@link OpenPgpElement} contains the encrypted and Base64 encoded {@link CryptElement} + * which can be decrypted by all recipients, as well as by ourselves. + * + * Note: DO NOT use this method in the context of instant messaging, as XEP-0374 forbids that. + * + * @see XEP-0374 §2.1 + * @param element plaintext {@link CryptElement} which will be encrypted. + * @param recipients {@link Set} of {@link BareJid} of recipients, which will be able to decrypt the message. + * @return {@link OpenPgpElement} which contains the encrypted, Base64 encoded {@link CryptElement}. + * @throws Exception + */ + OpenPgpElement encrypt(CryptElement element, Set recipients) throws Exception; + /** + * Decrypt an incoming {@link OpenPgpElement} which must contain a {@link CryptElement}. + * The resulting {@link OpenPgpMessage} will contain the decrypted {@link CryptElement}. + * + * Note: DO NOT use this method in the context of instant messaging, as XEP-0374 forbids that. + * + * @see XEP-0374 §2.1 + * @param element {@link OpenPgpElement} which contains the encrypted {@link CryptElement}. + * @return {@link OpenPgpMessage} which contains the decrypted {@link CryptElement}. + * @throws Exception + */ + OpenPgpMessage decrypt(OpenPgpElement element) throws Exception; + + /** + * 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 CorruptedOpenPgpKeyException if our public key can for some reason not be serialized. + */ PubkeyElement createPubkeyElement() throws CorruptedOpenPgpKeyException; - void processPubkeyElement(PubkeyElement element, BareJid from) throws CorruptedOpenPgpKeyException; + /** + * Process an incoming {@link PubkeyElement} of a contact or ourselves. + * That typically includes importing/updating the key. + * + * @param element {@link PubkeyElement} which presumably contains the public key of the {@code owner}. + * @param owner owner of the OpenPGP public key contained in the {@link PubkeyElement}. + * @throws CorruptedOpenPgpKeyException if the key found in the {@link PubkeyElement} + * can not be deserialized or imported. + */ + void processPubkeyElement(PubkeyElement element, BareJid owner) throws CorruptedOpenPgpKeyException; - void processPublicKeysListElement(PublicKeysListElement listElement, BareJid from) throws Exception; + /** + * Process an incoming update to the OpenPGP metadata node. + * That typically includes fetching announced keys of which we don't have a local copy yet, + * as well as marking keys which are missing from the list as inactive. + * + * @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. + * @throws Exception + */ + void processPublicKeysListElement(PublicKeysListElement listElement, BareJid owner) throws Exception; /** * Return the OpenPGP v4-fingerprint of our key in hexadecimal upper case.