1
0
Fork 0
mirror of https://github.com/vanitasvitae/Smack.git synced 2025-12-09 12:31:08 +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.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;
}

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 {
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();

View file

@ -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());
}
}

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
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());