mirror of
https://github.com/vanitasvitae/Smack.git
synced 2024-12-25 03:57:58 +01:00
Implement OpenPgpManager functions
This commit is contained in:
parent
e00075bca9
commit
6e7145801e
11 changed files with 415 additions and 101 deletions
|
@ -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();
|
||||
|
||||
}
|
|
@ -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.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.XmppKeySelectionStrategy;
|
||||
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.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
import org.bouncycastle.util.io.Streams;
|
||||
import org.jxmpp.jid.BareJid;
|
||||
|
||||
|
@ -56,51 +56,26 @@ public class BouncycastleOpenPgpProvider implements OpenPgpProvider {
|
|||
private final InMemoryKeyring ourKeys;
|
||||
private final Long ourKeyId;
|
||||
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 {
|
||||
this.ourJid = ourJid;
|
||||
PGPSecretKeyRing ourKey = generateKey(ourJid, null).generateSecretKeyRing();
|
||||
PGPSecretKeyRing ourKey = generateKey(ourJid).generateSecretKeyRing();
|
||||
ourKeyId = ourKey.getPublicKey().getKeyID();
|
||||
ourKeys = KeyringConfigs.forGpgExportedKeys(KeyringConfigCallbacks.withUnprotectedKeys());
|
||||
ourKeys.addSecretKey(ourKey.getSecretKey().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());
|
||||
|
||||
InMemoryKeyring contactsKeyring = theirKeys.get(jid);
|
||||
|
@ -112,13 +87,6 @@ public class BouncycastleOpenPgpProvider implements OpenPgpProvider {
|
|||
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
|
||||
public OpenPgpElement signAndEncrypt(InputStream inputStream, Set<BareJid> recipients)
|
||||
throws Exception {
|
||||
|
@ -170,12 +138,52 @@ public class BouncycastleOpenPgpProvider implements OpenPgpProvider {
|
|||
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()
|
||||
.withRSAKeys()
|
||||
.ofSize(PublicKeySize.RSA._2048)
|
||||
.forIdentity("xmpp:" + owner.toString())
|
||||
.withPassphrase(passPhrase)
|
||||
.withoutPassphrase()
|
||||
.build();
|
||||
return generator;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -116,8 +116,8 @@ public class BasicEncryptionTest extends SmackTestSuite {
|
|||
throws IOException, PGPException, NoSuchAlgorithmException, NoSuchProviderException, SignatureException {
|
||||
final String alice = "alice@wonderland.lit";
|
||||
final String bob = "bob@builder.tv";
|
||||
PGPKeyRingGenerator g1 = BouncycastleOpenPgpProvider.generateKey(JidCreate.bareFrom(alice), null);
|
||||
PGPKeyRingGenerator g2 = BouncycastleOpenPgpProvider.generateKey(JidCreate.bareFrom(bob), null);
|
||||
PGPKeyRingGenerator g1 = BouncycastleOpenPgpProvider.generateKey(JidCreate.bareFrom(alice));
|
||||
PGPKeyRingGenerator g2 = BouncycastleOpenPgpProvider.generateKey(JidCreate.bareFrom(bob));
|
||||
PGPSecretKey s1 = g1.generateSecretKeyRing().getSecretKey();
|
||||
PGPSecretKey s2 = g2.generateSecretKeyRing().getSecretKey();
|
||||
PGPPublicKey p1 = g1.generatePublicKeyRing().getPublicKey();
|
||||
|
|
|
@ -52,9 +52,10 @@ public class BouncycastleOpenPgpProviderTest extends SmackTestSuite {
|
|||
// dry exchange keys
|
||||
PubkeyElement aliceKeys = aliceProvider.createPubkeyElement();
|
||||
PubkeyElement cheshireKeys = cheshireProvider.createPubkeyElement();
|
||||
aliceProvider.processRecipientsPublicKey(cheshire, cheshireKeys);
|
||||
cheshireProvider.processRecipientsPublicKey(alice, aliceKeys);
|
||||
aliceProvider.processPubkeyElement(cheshireKeys, cheshire);
|
||||
cheshireProvider.processPubkeyElement(aliceKeys, alice);
|
||||
|
||||
// Create signed and encrypted message from alice to the cheshire cat
|
||||
SigncryptElement signcryptElement = new SigncryptElement(
|
||||
Collections.<Jid>singleton(cheshire),
|
||||
Collections.<ExtensionElement>singletonList(
|
||||
|
@ -63,10 +64,11 @@ public class BouncycastleOpenPgpProviderTest extends SmackTestSuite {
|
|||
signcryptElement.toInputStream(),
|
||||
Collections.singleton(cheshire));
|
||||
|
||||
// Decrypt the message as the cheshire cat
|
||||
OpenPgpMessage decrypted = cheshireProvider.decryptAndVerify(encrypted, alice);
|
||||
OpenPgpContentElement content = decrypted.getOpenPgpContentElement();
|
||||
assertTrue(content instanceof SigncryptElement);
|
||||
|
||||
assertTrue(content instanceof SigncryptElement);
|
||||
assertXMLEqual(signcryptElement.toXML(null).toString(), content.toXML(null).toString());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -29,7 +29,7 @@ public class KeyGenerationTest {
|
|||
@Test
|
||||
public void createSecretKey() throws Exception {
|
||||
PGPSecretKey secretKey = BouncycastleOpenPgpProvider
|
||||
.generateKey(JidCreate.bareFrom("alice@wonderland.lit"), null)
|
||||
.generateKey(JidCreate.bareFrom("alice@wonderland.lit"))
|
||||
.generateSecretKeyRing()
|
||||
.getSecretKey();
|
||||
assertEquals(PublicKeyType.RSA_GENERAL.getId(), secretKey.getPublicKey().getAlgorithm());
|
||||
|
|
|
@ -5,6 +5,7 @@ Smack API for XEP-0373: OpenPGP for XMPP."""
|
|||
// sourceSet.test of the core subproject
|
||||
dependencies {
|
||||
compile project(':smack-core')
|
||||
compile project(':smack-extensions')
|
||||
testCompile project(path: ":smack-core", configuration: "testRuntime")
|
||||
testCompile project(path: ":smack-core", configuration: "archives")
|
||||
}
|
||||
|
|
|
@ -16,27 +16,50 @@
|
|||
*/
|
||||
package org.jivesoftware.smackx.ox;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
import org.jivesoftware.smack.Manager;
|
||||
import org.jivesoftware.smack.SmackException;
|
||||
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.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_NOTIFY = PEP_NODE_PUBLIC_KEYS + "+notify";
|
||||
|
||||
public static String PEP_NODE_PUBLIC_KEY(String id) {
|
||||
return PEP_NODE_PUBLIC_KEYS + ":" + id;
|
||||
}
|
||||
|
||||
private static final Map<XMPPConnection, OpenPgpManager> INSTANCES = new WeakHashMap<>();
|
||||
private OpenPgpProvider provider;
|
||||
|
||||
private OpenPgpManager(XMPPConnection 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) {
|
||||
|
@ -48,17 +71,108 @@ public final class OpenPgpManager extends Manager {
|
|||
return manager;
|
||||
}
|
||||
|
||||
public static String bareJidToIdentity(BareJid jid) {
|
||||
return "xmpp:" + jid.toString();
|
||||
public void setOpenPgpProvider(OpenPgpProvider provider) {
|
||||
this.provider = provider;
|
||||
|
||||
}
|
||||
|
||||
public static OpenPgpMessage toOpenPgpMessage(InputStream is) {
|
||||
return null;
|
||||
}
|
||||
public void publishPublicKey() throws Exception {
|
||||
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
|
||||
// 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
|
||||
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) {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ import java.io.InputStream;
|
|||
import java.util.Set;
|
||||
|
||||
import org.jivesoftware.smackx.ox.element.OpenPgpElement;
|
||||
import org.jivesoftware.smackx.ox.element.PubkeyElement;
|
||||
|
||||
import org.jxmpp.jid.BareJid;
|
||||
|
||||
|
@ -28,4 +29,16 @@ public interface OpenPgpProvider {
|
|||
OpenPgpMessage decryptAndVerify(OpenPgpElement element, BareJid sender) 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;
|
||||
}
|
||||
|
|
|
@ -16,18 +16,8 @@
|
|||
*/
|
||||
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.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 {
|
||||
|
||||
|
@ -37,35 +27,10 @@ public class OpenPgpElement implements ExtensionElement {
|
|||
// Represents the OpenPGP message, but encoded using base64.
|
||||
private final String base64EncodedOpenPgpMessage;
|
||||
|
||||
private OpenPgpMessage openPgpMessage;
|
||||
|
||||
// Represents the OpenPGP message, but base64 decoded.
|
||||
private byte[] openPgpMessageBytes;
|
||||
|
||||
private OpenPgpContentElement openPgpContentElement;
|
||||
|
||||
public OpenPgpElement(String 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() {
|
||||
return base64EncodedOpenPgpMessage;
|
||||
}
|
||||
|
@ -86,10 +51,4 @@ public class OpenPgpElement implements ExtensionElement {
|
|||
xml.rightAngleBracket().append(base64EncodedOpenPgpMessage).closeElement(this);
|
||||
return xml;
|
||||
}
|
||||
|
||||
private void ensureOpenPgpMessageBytesSet() {
|
||||
if (openPgpMessageBytes != null) return;
|
||||
|
||||
openPgpMessageBytes = Base64.decode(base64EncodedOpenPgpMessage);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue