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:
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());
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue