1
0
Fork 0
mirror of https://github.com/vanitasvitae/Smack.git synced 2024-11-24 04:52:05 +01:00

Implement OpenPgpManager functions

This commit is contained in:
Paul Schaub 2018-05-21 12:44:27 +02:00
parent e00075bca9
commit 6e7145801e
11 changed files with 415 additions and 101 deletions

View file

@ -0,0 +1,26 @@
package org.jivesoftware.smackx.ox.bouncycastle;
import java.io.FileNotFoundException;
import java.io.IOException;
import org.jivesoftware.smackx.ox.element.PublicKeysListElement;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
import org.jxmpp.jid.BareJid;
public interface BouncyCastleIdentityStore {
void storePubkeyList(BareJid jid, PublicKeysListElement list) throws FileNotFoundException, IOException;
PublicKeysListElement loadPubkeyList(BareJid jid) throws FileNotFoundException, IOException;
void storePublicKeys(BareJid jid, PGPPublicKeyRingCollection keys);
PGPPublicKeyRingCollection loadPublicKeys(BareJid jid);
void storeSecretKeys(PGPSecretKeyRingCollection secretKeys);
PGPSecretKeyRingCollection loadSecretKeys();
}

View file

@ -36,7 +36,6 @@ import org.jivesoftware.smackx.ox.element.PubkeyElement;
import name.neuhalfen.projects.crypto.bouncycastle.openpgp.BouncyGPG; import name.neuhalfen.projects.crypto.bouncycastle.openpgp.BouncyGPG;
import name.neuhalfen.projects.crypto.bouncycastle.openpgp.algorithms.PublicKeySize; import name.neuhalfen.projects.crypto.bouncycastle.openpgp.algorithms.PublicKeySize;
import name.neuhalfen.projects.crypto.bouncycastle.openpgp.keys.callbacks.KeySelectionStrategy;
import name.neuhalfen.projects.crypto.bouncycastle.openpgp.keys.callbacks.KeyringConfigCallbacks; import name.neuhalfen.projects.crypto.bouncycastle.openpgp.keys.callbacks.KeyringConfigCallbacks;
import name.neuhalfen.projects.crypto.bouncycastle.openpgp.keys.callbacks.XmppKeySelectionStrategy; import name.neuhalfen.projects.crypto.bouncycastle.openpgp.keys.callbacks.XmppKeySelectionStrategy;
import name.neuhalfen.projects.crypto.bouncycastle.openpgp.keys.keyrings.InMemoryKeyring; import name.neuhalfen.projects.crypto.bouncycastle.openpgp.keys.keyrings.InMemoryKeyring;
@ -47,6 +46,7 @@ import org.bouncycastle.openpgp.PGPKeyRingGenerator;
import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.util.encoders.Hex;
import org.bouncycastle.util.io.Streams; import org.bouncycastle.util.io.Streams;
import org.jxmpp.jid.BareJid; import org.jxmpp.jid.BareJid;
@ -56,51 +56,26 @@ public class BouncycastleOpenPgpProvider implements OpenPgpProvider {
private final InMemoryKeyring ourKeys; private final InMemoryKeyring ourKeys;
private final Long ourKeyId; private final Long ourKeyId;
private final Map<BareJid, InMemoryKeyring> theirKeys = new HashMap<>(); private final Map<BareJid, InMemoryKeyring> theirKeys = new HashMap<>();
private final KeySelectionStrategy keySelectionStrategy = new XmppKeySelectionStrategy(new Date());
@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());
}
for (PGPSecretKeyRing s : ourKeys.getSecretKeyRings()) {
decryptionConfig.addSecretKey(s.getSecretKey().getEncoded());
}
// Add their keys to decryption config
for (PGPPublicKeyRing p : theirKeys.get(sender).getPublicKeyRings()) {
decryptionConfig.addPublicKey(p.getPublicKey().getEncoded());
}
ByteArrayInputStream encryptedIn = new ByteArrayInputStream(
element.getEncryptedBase64MessageContent().getBytes(Charset.forName("UTF-8")));
InputStream decrypted = BouncyGPG.decryptAndVerifyStream()
.withConfig(decryptionConfig)
.withKeySelectionStrategy(new XmppKeySelectionStrategy(new Date()))
.andValidateSomeoneSigned()
.fromEncryptedInputStream(encryptedIn);
ByteArrayOutputStream decryptedOut = new ByteArrayOutputStream();
Streams.pipeAll(decrypted, decryptedOut);
return new OpenPgpMessage(null, new String(decryptedOut.toByteArray(), Charset.forName("UTF-8")));
}
public BouncycastleOpenPgpProvider(BareJid ourJid) throws IOException, PGPException, NoSuchAlgorithmException { public BouncycastleOpenPgpProvider(BareJid ourJid) throws IOException, PGPException, NoSuchAlgorithmException {
this.ourJid = ourJid; this.ourJid = ourJid;
PGPSecretKeyRing ourKey = generateKey(ourJid, null).generateSecretKeyRing(); PGPSecretKeyRing ourKey = generateKey(ourJid).generateSecretKeyRing();
ourKeyId = ourKey.getPublicKey().getKeyID(); ourKeyId = ourKey.getPublicKey().getKeyID();
ourKeys = KeyringConfigs.forGpgExportedKeys(KeyringConfigCallbacks.withUnprotectedKeys()); ourKeys = KeyringConfigs.forGpgExportedKeys(KeyringConfigCallbacks.withUnprotectedKeys());
ourKeys.addSecretKey(ourKey.getSecretKey().getEncoded()); ourKeys.addSecretKey(ourKey.getSecretKey().getEncoded());
ourKeys.addPublicKey(ourKey.getPublicKey().getEncoded()); ourKeys.addPublicKey(ourKey.getPublicKey().getEncoded());
} }
public void processRecipientsPublicKey(BareJid jid, PubkeyElement element) throws IOException, PGPException { @Override
public PubkeyElement createPubkeyElement() throws IOException, PGPException {
PGPPublicKey pubKey = ourKeys.getPublicKeyRings().getPublicKey(ourKeyId);
PubkeyElement.PubkeyDataElement dataElement = new PubkeyElement.PubkeyDataElement(
Base64.encode(pubKey.getEncoded()));
return new PubkeyElement(dataElement, new Date());
}
@Override
public void processPubkeyElement(PubkeyElement element, BareJid jid) throws IOException, PGPException {
byte[] decoded = Base64.decode(element.getDataElement().getB64Data()); byte[] decoded = Base64.decode(element.getDataElement().getB64Data());
InMemoryKeyring contactsKeyring = theirKeys.get(jid); InMemoryKeyring contactsKeyring = theirKeys.get(jid);
@ -112,13 +87,6 @@ public class BouncycastleOpenPgpProvider implements OpenPgpProvider {
contactsKeyring.addPublicKey(decoded); contactsKeyring.addPublicKey(decoded);
} }
public PubkeyElement createPubkeyElement() throws IOException, PGPException {
PGPPublicKey pubKey = ourKeys.getPublicKeyRings().getPublicKey(ourKeyId);
PubkeyElement.PubkeyDataElement dataElement = new PubkeyElement.PubkeyDataElement(
Base64.encode(pubKey.getEncoded()));
return new PubkeyElement(dataElement, new Date());
}
@Override @Override
public OpenPgpElement signAndEncrypt(InputStream inputStream, Set<BareJid> recipients) public OpenPgpElement signAndEncrypt(InputStream inputStream, Set<BareJid> recipients)
throws Exception { throws Exception {
@ -170,12 +138,52 @@ public class BouncycastleOpenPgpProvider implements OpenPgpProvider {
return new OpenPgpElement(base64); return new OpenPgpElement(base64);
} }
public static PGPKeyRingGenerator generateKey(BareJid owner, char[] passPhrase) throws NoSuchAlgorithmException, PGPException { @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());
}
for (PGPSecretKeyRing s : ourKeys.getSecretKeyRings()) {
decryptionConfig.addSecretKey(s.getSecretKey().getEncoded());
}
// Add their keys to decryption config
for (PGPPublicKeyRing p : theirKeys.get(sender).getPublicKeyRings()) {
decryptionConfig.addPublicKey(p.getPublicKey().getEncoded());
}
ByteArrayInputStream encryptedIn = new ByteArrayInputStream(
element.getEncryptedBase64MessageContent().getBytes(Charset.forName("UTF-8")));
InputStream decrypted = BouncyGPG.decryptAndVerifyStream()
.withConfig(decryptionConfig)
.withKeySelectionStrategy(new XmppKeySelectionStrategy(new Date()))
.andValidateSomeoneSigned()
.fromEncryptedInputStream(encryptedIn);
ByteArrayOutputStream decryptedOut = new ByteArrayOutputStream();
Streams.pipeAll(decrypted, decryptedOut);
return new OpenPgpMessage(null, new String(decryptedOut.toByteArray(), Charset.forName("UTF-8")));
}
@Override
public String getFingerprint() throws IOException, PGPException {
return new String(Hex.encode(ourKeys.getKeyFingerPrintCalculator()
.calculateFingerprint(ourKeys.getPublicKeyRings().getPublicKey(ourKeyId)
.getPublicKeyPacket())), Charset.forName("UTF-8")).toUpperCase();
}
public static PGPKeyRingGenerator generateKey(BareJid owner) throws NoSuchAlgorithmException, PGPException {
PGPKeyRingGenerator generator = BouncyGPG.createKeyPair() PGPKeyRingGenerator generator = BouncyGPG.createKeyPair()
.withRSAKeys() .withRSAKeys()
.ofSize(PublicKeySize.RSA._2048) .ofSize(PublicKeySize.RSA._2048)
.forIdentity("xmpp:" + owner.toString()) .forIdentity("xmpp:" + owner.toString())
.withPassphrase(passPhrase) .withoutPassphrase()
.build(); .build();
return generator; return generator;
} }

View file

@ -0,0 +1,105 @@
package org.jivesoftware.smackx.ox.bouncycastle;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.ParseException;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smackx.ox.element.PublicKeysListElement;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
import org.jxmpp.jid.BareJid;
import org.jxmpp.util.XmppDateTime;
public class FileBasedBouncyCastleIdentityStore implements BouncyCastleIdentityStore {
private static final Logger LOGGER = Logger.getLogger(FileBasedBouncyCastleIdentityStore.class.getName());
private final File baseDirectory;
public FileBasedBouncyCastleIdentityStore(File path) {
// Check if path is not null, not a file and directory exists
if (!Objects.requireNonNull(path).exists()) {
path.mkdirs();
} else if (path.isFile()) {
throw new IllegalArgumentException("Path MUST point to a directory, not a file.");
}
this.baseDirectory = path;
}
@Override
public void storePubkeyList(BareJid jid, PublicKeysListElement list) throws IOException {
File contactsDir = contactsDir(jid);
File destination = new File(contactsDir, "pubkey_list");
DataOutputStream dataOut = new DataOutputStream(new FileOutputStream(destination));
for (String key : list.getMetadata().keySet()) {
PublicKeysListElement.PubkeyMetadataElement value = list.getMetadata().get(key);
dataOut.writeUTF(value.getV4Fingerprint());
dataOut.writeUTF(XmppDateTime.formatXEP0082Date(value.getDate()));
}
dataOut.close();
}
@Override
public PublicKeysListElement loadPubkeyList(BareJid jid) throws IOException {
File contactsDir = contactsDir(jid);
File source = new File(contactsDir, "pubkey_list");
DataInputStream dataIn = new DataInputStream(new FileInputStream(source));
PublicKeysListElement.Builder builder = PublicKeysListElement.builder();
while (true) {
try {
String fingerprint = dataIn.readUTF();
String d = dataIn.readUTF();
Date date = XmppDateTime.parseXEP0082Date(d);
builder.addMetadata(new PublicKeysListElement.PubkeyMetadataElement(fingerprint, date));
} catch (ParseException e) {
LOGGER.log(Level.WARNING, "Could not parse date.", e);
} catch (EOFException e) {
LOGGER.log(Level.INFO, "Reached EOF.");
break;
}
}
return builder.build();
}
@Override
public void storePublicKeys(BareJid jid, PGPPublicKeyRingCollection keys) {
}
@Override
public PGPPublicKeyRingCollection loadPublicKeys(BareJid jid) {
return null;
}
@Override
public void storeSecretKeys(PGPSecretKeyRingCollection secretKeys) {
}
@Override
public PGPSecretKeyRingCollection loadSecretKeys() {
return null;
}
private File contactsDir(BareJid contact) {
File f = new File(baseDirectory, "contacts/" + contact.toString());
if (!f.exists()) {
f.mkdirs();
}
return f;
}
}

View file

@ -116,8 +116,8 @@ public class BasicEncryptionTest extends SmackTestSuite {
throws IOException, PGPException, NoSuchAlgorithmException, NoSuchProviderException, SignatureException { throws IOException, PGPException, NoSuchAlgorithmException, NoSuchProviderException, SignatureException {
final String alice = "alice@wonderland.lit"; final String alice = "alice@wonderland.lit";
final String bob = "bob@builder.tv"; final String bob = "bob@builder.tv";
PGPKeyRingGenerator g1 = BouncycastleOpenPgpProvider.generateKey(JidCreate.bareFrom(alice), null); PGPKeyRingGenerator g1 = BouncycastleOpenPgpProvider.generateKey(JidCreate.bareFrom(alice));
PGPKeyRingGenerator g2 = BouncycastleOpenPgpProvider.generateKey(JidCreate.bareFrom(bob), null); PGPKeyRingGenerator g2 = BouncycastleOpenPgpProvider.generateKey(JidCreate.bareFrom(bob));
PGPSecretKey s1 = g1.generateSecretKeyRing().getSecretKey(); PGPSecretKey s1 = g1.generateSecretKeyRing().getSecretKey();
PGPSecretKey s2 = g2.generateSecretKeyRing().getSecretKey(); PGPSecretKey s2 = g2.generateSecretKeyRing().getSecretKey();
PGPPublicKey p1 = g1.generatePublicKeyRing().getPublicKey(); PGPPublicKey p1 = g1.generatePublicKeyRing().getPublicKey();

View file

@ -52,9 +52,10 @@ public class BouncycastleOpenPgpProviderTest extends SmackTestSuite {
// dry exchange keys // dry exchange keys
PubkeyElement aliceKeys = aliceProvider.createPubkeyElement(); PubkeyElement aliceKeys = aliceProvider.createPubkeyElement();
PubkeyElement cheshireKeys = cheshireProvider.createPubkeyElement(); PubkeyElement cheshireKeys = cheshireProvider.createPubkeyElement();
aliceProvider.processRecipientsPublicKey(cheshire, cheshireKeys); aliceProvider.processPubkeyElement(cheshireKeys, cheshire);
cheshireProvider.processRecipientsPublicKey(alice, aliceKeys); cheshireProvider.processPubkeyElement(aliceKeys, alice);
// Create signed and encrypted message from alice to the cheshire cat
SigncryptElement signcryptElement = new SigncryptElement( SigncryptElement signcryptElement = new SigncryptElement(
Collections.<Jid>singleton(cheshire), Collections.<Jid>singleton(cheshire),
Collections.<ExtensionElement>singletonList( Collections.<ExtensionElement>singletonList(
@ -63,10 +64,11 @@ public class BouncycastleOpenPgpProviderTest extends SmackTestSuite {
signcryptElement.toInputStream(), signcryptElement.toInputStream(),
Collections.singleton(cheshire)); Collections.singleton(cheshire));
// Decrypt the message as the cheshire cat
OpenPgpMessage decrypted = cheshireProvider.decryptAndVerify(encrypted, alice); OpenPgpMessage decrypted = cheshireProvider.decryptAndVerify(encrypted, alice);
OpenPgpContentElement content = decrypted.getOpenPgpContentElement(); OpenPgpContentElement content = decrypted.getOpenPgpContentElement();
assertTrue(content instanceof SigncryptElement);
assertTrue(content instanceof SigncryptElement);
assertXMLEqual(signcryptElement.toXML(null).toString(), content.toXML(null).toString()); assertXMLEqual(signcryptElement.toXML(null).toString(), content.toXML(null).toString());
} }
} }

View file

@ -0,0 +1,86 @@
package org.jivesoftware.smackx.ox.bouncycastle;
import static junit.framework.TestCase.assertEquals;
import java.io.File;
import java.io.IOException;
import java.text.ParseException;
import java.util.Date;
import java.util.Stack;
import org.jivesoftware.smack.test.util.SmackTestSuite;
import org.jivesoftware.smackx.ox.element.PublicKeysListElement;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.jxmpp.jid.BareJid;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.util.XmppDateTime;
public class FileBasedBouncyCastleIdentityStoreTest extends SmackTestSuite {
public static File storePath;
@BeforeClass
public static void setup() {
String userHome = System.getProperty("user.home");
if (userHome != null) {
File f = new File(userHome);
storePath = new File(f, ".config/smack-integration-test/ox_store");
} else {
storePath = new File("int_test_ox_store");
}
}
@Before
public void before() {
deleteStore();
}
@After
public void after() {
deleteStore();
}
public void deleteStore() {
File[] currList;
Stack<File> stack = new Stack<>();
stack.push(storePath);
while (!stack.isEmpty()) {
if (stack.lastElement().isDirectory()) {
currList = stack.lastElement().listFiles();
if (currList != null && currList.length > 0) {
for (File curr : currList) {
stack.push(curr);
}
} else {
stack.pop().delete();
}
} else {
stack.pop().delete();
}
}
}
@Test
public void writeReadPublicKeysLists() throws ParseException, IOException {
BareJid jid = JidCreate.bareFrom("edward@snowden.org");
String fp1 = "1357B01865B2503C18453D208CAC2A9678548E35";
String fp2 = "67819B343B2AB70DED9320872C6464AF2A8E4C02";
Date d1 = XmppDateTime.parseDate("2018-03-01T15:26:12Z");
Date d2 = XmppDateTime.parseDate("1953-05-16T12:00:00Z");
PublicKeysListElement list = PublicKeysListElement.builder()
.addMetadata(new PublicKeysListElement.PubkeyMetadataElement(fp1, d1))
.addMetadata(new PublicKeysListElement.PubkeyMetadataElement(fp2, d2))
.build();
FileBasedBouncyCastleIdentityStore store = new FileBasedBouncyCastleIdentityStore(storePath);
store.storePubkeyList(jid, list);
PublicKeysListElement retrieved = store.loadPubkeyList(jid);
assertEquals(list.getMetadata(), retrieved.getMetadata());
}
}

View file

@ -29,7 +29,7 @@ public class KeyGenerationTest {
@Test @Test
public void createSecretKey() throws Exception { public void createSecretKey() throws Exception {
PGPSecretKey secretKey = BouncycastleOpenPgpProvider PGPSecretKey secretKey = BouncycastleOpenPgpProvider
.generateKey(JidCreate.bareFrom("alice@wonderland.lit"), null) .generateKey(JidCreate.bareFrom("alice@wonderland.lit"))
.generateSecretKeyRing() .generateSecretKeyRing()
.getSecretKey(); .getSecretKey();
assertEquals(PublicKeyType.RSA_GENERAL.getId(), secretKey.getPublicKey().getAlgorithm()); assertEquals(PublicKeyType.RSA_GENERAL.getId(), secretKey.getPublicKey().getAlgorithm());

View file

@ -5,6 +5,7 @@ Smack API for XEP-0373: OpenPGP for XMPP."""
// sourceSet.test of the core subproject // sourceSet.test of the core subproject
dependencies { dependencies {
compile project(':smack-core') compile project(':smack-core')
compile project(':smack-extensions')
testCompile project(path: ":smack-core", configuration: "testRuntime") testCompile project(path: ":smack-core", configuration: "testRuntime")
testCompile project(path: ":smack-core", configuration: "archives") testCompile project(path: ":smack-core", configuration: "archives")
} }

View file

@ -16,27 +16,50 @@
*/ */
package org.jivesoftware.smackx.ox; package org.jivesoftware.smackx.ox;
import java.io.InputStream; import java.util.Date;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.WeakHashMap; import java.util.WeakHashMap;
import org.jivesoftware.smack.Manager; import org.jivesoftware.smack.Manager;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.ox.element.PubkeyElement;
import org.jivesoftware.smackx.ox.element.PublicKeysListElement;
import org.jivesoftware.smackx.pep.PEPListener;
import org.jivesoftware.smackx.pep.PEPManager;
import org.jivesoftware.smackx.pubsub.EventElement;
import org.jivesoftware.smackx.pubsub.Item;
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.BareJid; import org.jxmpp.jid.BareJid;
import org.jxmpp.jid.EntityBareJid;
public final class OpenPgpManager extends Manager { public final class OpenPgpManager extends Manager implements PEPListener {
public static final String PEP_NODE_PUBLIC_KEYS = "urn:xmpp:openpgp:0:public-keys"; public static final String PEP_NODE_PUBLIC_KEYS = "urn:xmpp:openpgp:0:public-keys";
public static final String PEP_NODE_PUBLIC_KEYS_NOTIFY = PEP_NODE_PUBLIC_KEYS + "+notify";
public static String PEP_NODE_PUBLIC_KEY(String id) { public static String PEP_NODE_PUBLIC_KEY(String id) {
return PEP_NODE_PUBLIC_KEYS + ":" + id; return PEP_NODE_PUBLIC_KEYS + ":" + id;
} }
private static final Map<XMPPConnection, OpenPgpManager> INSTANCES = new WeakHashMap<>(); private static final Map<XMPPConnection, OpenPgpManager> INSTANCES = new WeakHashMap<>();
private OpenPgpProvider provider;
private OpenPgpManager(XMPPConnection connection) { private OpenPgpManager(XMPPConnection connection) {
super(connection); super(connection);
// Subscribe to public key changes
PEPManager.getInstanceFor(connection()).addPEPListener(this);
ServiceDiscoveryManager.getInstanceFor(connection())
.addFeature(PEP_NODE_PUBLIC_KEYS_NOTIFY);
} }
public static OpenPgpManager getInstanceFor(XMPPConnection connection) { public static OpenPgpManager getInstanceFor(XMPPConnection connection) {
@ -48,17 +71,108 @@ public final class OpenPgpManager extends Manager {
return manager; return manager;
} }
public static String bareJidToIdentity(BareJid jid) { public void setOpenPgpProvider(OpenPgpProvider provider) {
return "xmpp:" + jid.toString(); this.provider = provider;
} }
public static OpenPgpMessage toOpenPgpMessage(InputStream is) { public void publishPublicKey() throws Exception {
return null; ensureProviderIsSet();
} PubkeyElement pubkeyElement = provider.createPubkeyElement();
String fingerprint = provider.getFingerprint();
String keyNodeName = PEP_NODE_PUBLIC_KEY(fingerprint);
PubSubManager pm = PubSubManager.getInstance(connection(), connection().getUser().asBareJid());
public void publishPublicKey() {
// Check if key available at data node // Check if key available at data node
// If not, publish key to data node // If not, publish key to data node
LeafNode keyNode = pm.getOrCreateLeafNode(keyNodeName);
List<Item> items = keyNode.getItems(1);
if (items.isEmpty()) {
keyNode.publish(new PayloadItem<>(pubkeyElement));
}
// Publish ID to metadata node // Publish ID to metadata node
LeafNode metadataNode = pm.getOrCreateLeafNode(PEP_NODE_PUBLIC_KEYS);
List<PayloadItem<PublicKeysListElement>> metadataItems = metadataNode.getItems(1);
PublicKeysListElement.Builder builder = PublicKeysListElement.builder();
if (!metadataItems.isEmpty() && metadataItems.get(0).getPayload() != null) {
// Add old entries back to list.
PublicKeysListElement publishedList = metadataItems.get(0).getPayload();
for (PublicKeysListElement.PubkeyMetadataElement meta : publishedList.getMetadata().values()) {
builder.addMetadata(meta);
}
}
builder.addMetadata(new PublicKeysListElement.PubkeyMetadataElement(fingerprint, new Date()));
metadataNode.publish(new PayloadItem<>(builder.build()));
}
public PublicKeysListElement fetchPubkeysList()
throws InterruptedException, PubSubException.NotALeafNodeException, SmackException.NoResponseException,
SmackException.NotConnectedException, XMPPException.XMPPErrorException,
PubSubException.NotAPubSubNodeException {
return fetchPubkeysList(connection().getUser().asBareJid());
}
public PublicKeysListElement fetchPubkeysList(BareJid jid)
throws InterruptedException, PubSubException.NotALeafNodeException, SmackException.NoResponseException,
SmackException.NotConnectedException, XMPPException.XMPPErrorException,
PubSubException.NotAPubSubNodeException
{
PubSubManager pm = PubSubManager.getInstance(connection(), jid);
LeafNode node = pm.getLeafNode(PEP_NODE_PUBLIC_KEYS);
List<PayloadItem<PublicKeysListElement>> list = node.getItems(1);
if (list.isEmpty()) {
return null;
}
return list.get(0).getPayload();
}
public PubkeyElement fetchPubkey(BareJid jid, String v4_fingerprint)
throws InterruptedException, PubSubException.NotALeafNodeException, SmackException.NoResponseException,
SmackException.NotConnectedException, XMPPException.XMPPErrorException,
PubSubException.NotAPubSubNodeException {
PubSubManager pm = PubSubManager.getInstance(connection(), jid);
LeafNode node = pm.getLeafNode(PEP_NODE_PUBLIC_KEY(v4_fingerprint));
List<PayloadItem<PubkeyElement>> list = node.getItems(1);
if (list.isEmpty()) {
return null;
}
return list.get(0).getPayload();
}
public void depositSecretKey() {
ensureProviderIsSet();
// Create key backup by appending serialized unencrypted secret keys.
// Encrypt the backup using a random generated password
// Publish the backup to the secret key node (whitelist protected)
// Display the backup key to the user
}
public String getOurFingerprint() throws Exception {
ensureProviderIsSet();
return provider.getFingerprint();
}
/**
* Throw an {@link IllegalStateException} if no {@link OpenPgpProvider} is set.
*/
private void ensureProviderIsSet() {
if (provider == null) {
throw new IllegalStateException("No OpenPgpProvider set!");
}
}
@Override
public void eventReceived(EntityBareJid from, EventElement event, Message message) {
} }
} }

View file

@ -20,6 +20,7 @@ import java.io.InputStream;
import java.util.Set; import java.util.Set;
import org.jivesoftware.smackx.ox.element.OpenPgpElement; import org.jivesoftware.smackx.ox.element.OpenPgpElement;
import org.jivesoftware.smackx.ox.element.PubkeyElement;
import org.jxmpp.jid.BareJid; import org.jxmpp.jid.BareJid;
@ -28,4 +29,16 @@ public interface OpenPgpProvider {
OpenPgpMessage decryptAndVerify(OpenPgpElement element, BareJid sender) throws Exception; OpenPgpMessage decryptAndVerify(OpenPgpElement element, BareJid sender) throws Exception;
OpenPgpElement signAndEncrypt(InputStream inputStream, Set<BareJid> recipients) throws Exception; OpenPgpElement signAndEncrypt(InputStream inputStream, Set<BareJid> recipients) throws Exception;
PubkeyElement createPubkeyElement() throws Exception;
void processPubkeyElement(PubkeyElement element, BareJid from) throws Exception;
/**
* Return the OpenPGP v4-fingerprint of our key in hexadecimal upper case.
*
* @return fingerprint
* @throws Exception
*/
String getFingerprint() throws Exception;
} }

View file

@ -16,18 +16,8 @@
*/ */
package org.jivesoftware.smackx.ox.element; package org.jivesoftware.smackx.ox.element;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import org.jivesoftware.smack.packet.ExtensionElement; import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.util.XmlStringBuilder; import org.jivesoftware.smack.util.XmlStringBuilder;
import org.jivesoftware.smack.util.stringencoder.Base64;
import org.jivesoftware.smackx.ox.OpenPgpManager;
import org.jivesoftware.smackx.ox.OpenPgpMessage;
import org.jivesoftware.smackx.ox.OpenPgpProvider;
import org.xmlpull.v1.XmlPullParserException;
public class OpenPgpElement implements ExtensionElement { public class OpenPgpElement implements ExtensionElement {
@ -37,35 +27,10 @@ public class OpenPgpElement implements ExtensionElement {
// Represents the OpenPGP message, but encoded using base64. // Represents the OpenPGP message, but encoded using base64.
private final String base64EncodedOpenPgpMessage; private final String base64EncodedOpenPgpMessage;
private OpenPgpMessage openPgpMessage;
// Represents the OpenPGP message, but base64 decoded.
private byte[] openPgpMessageBytes;
private OpenPgpContentElement openPgpContentElement;
public OpenPgpElement(String base64EncodedOpenPgpMessage) { public OpenPgpElement(String base64EncodedOpenPgpMessage) {
this.base64EncodedOpenPgpMessage = base64EncodedOpenPgpMessage; this.base64EncodedOpenPgpMessage = base64EncodedOpenPgpMessage;
} }
public OpenPgpMessage getOpenPgpMessage(OpenPgpProvider provider) {
if (openPgpMessage == null) {
ensureOpenPgpMessageBytesSet();
InputStream is = new ByteArrayInputStream(openPgpMessageBytes);
openPgpMessage = OpenPgpManager.toOpenPgpMessage(is);
}
return openPgpMessage;
}
public OpenPgpContentElement getContentElement(OpenPgpProvider provider) throws XmlPullParserException, IOException {
if (openPgpContentElement == null) {
openPgpContentElement = getOpenPgpMessage(provider).getOpenPgpContentElement();
}
return openPgpContentElement;
}
public String getEncryptedBase64MessageContent() { public String getEncryptedBase64MessageContent() {
return base64EncodedOpenPgpMessage; return base64EncodedOpenPgpMessage;
} }
@ -86,10 +51,4 @@ public class OpenPgpElement implements ExtensionElement {
xml.rightAngleBracket().append(base64EncodedOpenPgpMessage).closeElement(this); xml.rightAngleBracket().append(base64EncodedOpenPgpMessage).closeElement(this);
return xml; return xml;
} }
private void ensureOpenPgpMessageBytesSet() {
if (openPgpMessageBytes != null) return;
openPgpMessageBytes = Base64.decode(base64EncodedOpenPgpMessage);
}
} }