mirror of
https://github.com/vanitasvitae/Smack.git
synced 2024-11-24 04:52:05 +01:00
BouncycastleOpenPgpProvider implementatioN
This commit is contained in:
parent
2acf9689fe
commit
9ee47e1711
10 changed files with 346 additions and 88 deletions
|
@ -16,108 +16,168 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.ox.bouncycastle;
|
package org.jivesoftware.smackx.ox.bouncycastle;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.security.KeyPair;
|
|
||||||
import java.security.KeyPairGenerator;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.NoSuchProviderException;
|
|
||||||
import java.security.Provider;
|
|
||||||
import java.security.SignatureException;
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Iterator;
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.util.stringencoder.Base64;
|
||||||
import org.jivesoftware.smackx.ox.OpenPgpMessage;
|
import org.jivesoftware.smackx.ox.OpenPgpMessage;
|
||||||
import org.jivesoftware.smackx.ox.OpenPgpProvider;
|
import org.jivesoftware.smackx.ox.OpenPgpProvider;
|
||||||
|
import org.jivesoftware.smackx.ox.element.OpenPgpElement;
|
||||||
|
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.keys.callbacks.KeySelectionStrategy;
|
import name.neuhalfen.projects.crypto.bouncycastle.openpgp.keys.callbacks.KeySelectionStrategy;
|
||||||
import name.neuhalfen.projects.crypto.bouncycastle.openpgp.keys.callbacks.Xep0373KeySelectionStrategy;
|
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;
|
||||||
import name.neuhalfen.projects.crypto.bouncycastle.openpgp.keys.keyrings.KeyringConfig;
|
import name.neuhalfen.projects.crypto.bouncycastle.openpgp.keys.keyrings.KeyringConfig;
|
||||||
import org.bouncycastle.bcpg.HashAlgorithmTags;
|
import name.neuhalfen.projects.crypto.bouncycastle.openpgp.keys.keyrings.KeyringConfigs;
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
|
||||||
import org.bouncycastle.openpgp.PGPEncryptedData;
|
|
||||||
import org.bouncycastle.openpgp.PGPException;
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
import org.bouncycastle.openpgp.PGPKeyPair;
|
import org.bouncycastle.openpgp.PGPKeyRingGenerator;
|
||||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||||
import org.bouncycastle.openpgp.PGPSignature;
|
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||||
import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
|
|
||||||
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
|
|
||||||
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
|
|
||||||
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyPair;
|
|
||||||
import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder;
|
|
||||||
import org.bouncycastle.util.io.Streams;
|
import org.bouncycastle.util.io.Streams;
|
||||||
import org.jxmpp.jid.BareJid;
|
import org.jxmpp.jid.BareJid;
|
||||||
import org.jxmpp.jid.Jid;
|
|
||||||
|
|
||||||
public class BouncycastleOpenPgpProvider implements OpenPgpProvider {
|
public class BouncycastleOpenPgpProvider implements OpenPgpProvider {
|
||||||
|
|
||||||
public static final Provider PROVIDER = new BouncyCastleProvider();
|
private final BareJid ourJid;
|
||||||
|
private final InMemoryKeyring ourKeys;
|
||||||
|
private final Long ourKeyId;
|
||||||
|
private final Map<BareJid, InMemoryKeyring> theirKeys = new HashMap<>();
|
||||||
|
private final KeySelectionStrategy keySelectionStrategy = new XmppKeySelectionStrategy(new Date());
|
||||||
|
|
||||||
private final KeyringConfig keyringConfig;
|
@Override
|
||||||
private final BareJid signingIdentity;
|
public OpenPgpMessage decryptAndVerify(OpenPgpElement element, BareJid sender) throws Exception {
|
||||||
private final KeySelectionStrategy keySelectionStrategy = new Xep0373KeySelectionStrategy(new Date());
|
InMemoryKeyring decryptionConfig = KeyringConfigs.forGpgExportedKeys(KeyringConfigCallbacks.withUnprotectedKeys());
|
||||||
|
|
||||||
public BouncycastleOpenPgpProvider(KeyringConfig config, BareJid signingIdentity) throws IOException, PGPException {
|
// Add our keys to decryption config
|
||||||
this.keyringConfig = config;
|
for (PGPPublicKeyRing p : ourKeys.getPublicKeyRings()) {
|
||||||
this.signingIdentity = signingIdentity;
|
decryptionConfig.addPublicKey(p.getPublicKey().getEncoded());
|
||||||
|
}
|
||||||
|
for (PGPSecretKeyRing s : ourKeys.getSecretKeyRings()) {
|
||||||
|
decryptionConfig.addSecretKey(s.getSecretKey().getEncoded());
|
||||||
}
|
}
|
||||||
|
|
||||||
public OpenPgpMessage toOpenPgpMessage(InputStream is, Set<Jid> recipients, Jid signer)
|
// Add their keys to decryption config
|
||||||
throws IOException, PGPException, NoSuchAlgorithmException, SignatureException, NoSuchProviderException {
|
for (PGPPublicKeyRing p : theirKeys.get(sender).getPublicKeyRings()) {
|
||||||
|
decryptionConfig.addPublicKey(p.getPublicKey().getEncoded());
|
||||||
String[] to = new String[recipients.size()];
|
|
||||||
Iterator<Jid> it = recipients.iterator();
|
|
||||||
for (int i = 0; i<recipients.size(); i++) {
|
|
||||||
to[i] = "xmpp:" + it.next().asBareJid().toString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
OutputStream resultStream = new ByteArrayOutputStream();
|
ByteArrayInputStream encryptedIn = new ByteArrayInputStream(
|
||||||
|
element.getEncryptedBase64MessageContent().getBytes(Charset.forName("UTF-8")));
|
||||||
|
|
||||||
OutputStream os = BouncyGPG.encryptToStream()
|
InputStream decrypted = BouncyGPG.decryptAndVerifyStream()
|
||||||
.withConfig(keyringConfig)
|
.withConfig(decryptionConfig)
|
||||||
.withKeySelectionStrategy(keySelectionStrategy)
|
.withKeySelectionStrategy(new XmppKeySelectionStrategy(new Date()))
|
||||||
.withOxAlgorithms()
|
.andValidateSomeoneSigned()
|
||||||
.toRecipients(to)
|
.fromEncryptedInputStream(encryptedIn);
|
||||||
.andSignWith("xmpp:" + signer.asBareJid().toString())
|
|
||||||
.binaryOutput()
|
|
||||||
.andWriteTo(resultStream);
|
|
||||||
|
|
||||||
Streams.pipeAll(is, os);
|
ByteArrayOutputStream decryptedOut = new ByteArrayOutputStream();
|
||||||
os.close();
|
|
||||||
|
|
||||||
byte[] encrypted = ((ByteArrayOutputStream) resultStream).toByteArray();
|
Streams.pipeAll(decrypted, decryptedOut);
|
||||||
return new OpenPgpMessage(OpenPgpMessage.State.signcrypt, new String(encrypted, Charset.forName("UTF-8")));
|
|
||||||
|
return new OpenPgpMessage(null, new String(decryptedOut.toByteArray(), Charset.forName("UTF-8")));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PGPSecretKey generateKey(BareJid owner, char[] passPhrase) throws NoSuchAlgorithmException, PGPException {
|
public BouncycastleOpenPgpProvider(BareJid ourJid) throws IOException, PGPException, NoSuchAlgorithmException {
|
||||||
// Create RSA Key Pair
|
this.ourJid = ourJid;
|
||||||
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", PROVIDER);
|
PGPSecretKeyRing ourKey = generateKey(ourJid, null).generateSecretKeyRing();
|
||||||
generator.initialize(2048);
|
ourKeyId = ourKey.getPublicKey().getKeyID();
|
||||||
KeyPair rsaPair = generator.generateKeyPair();
|
ourKeys = KeyringConfigs.forGpgExportedKeys(KeyringConfigCallbacks.withUnprotectedKeys());
|
||||||
|
ourKeys.addSecretKey(ourKey.getSecretKey().getEncoded());
|
||||||
|
ourKeys.addPublicKey(ourKey.getPublicKey().getEncoded());
|
||||||
|
}
|
||||||
|
|
||||||
PGPDigestCalculator calculator = new JcaPGPDigestCalculatorProviderBuilder()
|
public void processRecipientsPublicKey(BareJid jid, PubkeyElement element) throws IOException, PGPException {
|
||||||
.setProvider(PROVIDER)
|
byte[] decoded = Base64.decode(element.getDataElement().getB64Data());
|
||||||
.build()
|
|
||||||
.get(HashAlgorithmTags.SHA256);
|
|
||||||
|
|
||||||
PGPKeyPair pgpPair = new JcaPGPKeyPair(PGPPublicKey.RSA_GENERAL, rsaPair, new Date());
|
InMemoryKeyring contactsKeyring = theirKeys.get(jid);
|
||||||
PGPSecretKey secretKey = new PGPSecretKey(PGPSignature.DEFAULT_CERTIFICATION,
|
if (contactsKeyring == null) {
|
||||||
pgpPair, "xmpp:" + owner.toString(), calculator, null, null,
|
contactsKeyring = KeyringConfigs.forGpgExportedKeys(KeyringConfigCallbacks.withUnprotectedKeys());
|
||||||
new JcaPGPContentSignerBuilder(pgpPair.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA256),
|
theirKeys.put(jid, contactsKeyring);
|
||||||
new JcePBESecretKeyEncryptorBuilder(PGPEncryptedData.AES_256, calculator)
|
}
|
||||||
.setProvider(PROVIDER).build(passPhrase));
|
|
||||||
|
|
||||||
return secretKey;
|
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 OpenPgpMessage toOpenPgpMessage(InputStream is) {
|
public OpenPgpElement signAndEncrypt(InputStream inputStream, Set<BareJid> recipients)
|
||||||
return null;
|
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 keys to encryption config
|
||||||
|
for (PGPPublicKeyRing p : ourKeys.getPublicKeyRings()) {
|
||||||
|
encryptionConfig.addPublicKey(p.getPublicKey().getEncoded());
|
||||||
|
}
|
||||||
|
for (PGPSecretKeyRing s : ourKeys.getSecretKeyRings()) {
|
||||||
|
encryptionConfig.addSecretKey(s.getSecretKey().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();
|
||||||
|
|
||||||
|
ByteArrayOutputStream encryptedOut = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
OutputStream encryptor = BouncyGPG.encryptToStream()
|
||||||
|
.withConfig(encryptionConfig)
|
||||||
|
.withKeySelectionStrategy(new XmppKeySelectionStrategy(new Date()))
|
||||||
|
.withOxAlgorithms()
|
||||||
|
.toRecipients(recipientUIDs)
|
||||||
|
.andSignWith("xmpp:" + ourJid.toString())
|
||||||
|
.binaryOutput()
|
||||||
|
.andWriteTo(encryptedOut);
|
||||||
|
|
||||||
|
Streams.pipeAll(inputStream, encryptor);
|
||||||
|
encryptor.close();
|
||||||
|
|
||||||
|
String base64 = Base64.encodeToString(encryptedOut.toByteArray());
|
||||||
|
|
||||||
|
return new OpenPgpElement(base64);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PGPKeyRingGenerator generateKey(BareJid owner, char[] passPhrase) throws NoSuchAlgorithmException, PGPException {
|
||||||
|
PGPKeyRingGenerator generator = BouncyGPG.createKeyPair()
|
||||||
|
.withRSAKeys()
|
||||||
|
.ofSize(PublicKeySize.RSA._2048)
|
||||||
|
.forIdentity("xmpp:" + owner.toString())
|
||||||
|
.withPassphrase(passPhrase)
|
||||||
|
.build();
|
||||||
|
return generator;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,13 +38,17 @@ import org.jivesoftware.smackx.ox.TestKeys;
|
||||||
import name.neuhalfen.projects.crypto.bouncycastle.openpgp.BouncyGPG;
|
import name.neuhalfen.projects.crypto.bouncycastle.openpgp.BouncyGPG;
|
||||||
import name.neuhalfen.projects.crypto.bouncycastle.openpgp.keys.callbacks.KeySelectionStrategy;
|
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.Xep0373KeySelectionStrategy;
|
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;
|
||||||
import name.neuhalfen.projects.crypto.bouncycastle.openpgp.keys.keyrings.KeyringConfig;
|
import name.neuhalfen.projects.crypto.bouncycastle.openpgp.keys.keyrings.KeyringConfig;
|
||||||
import name.neuhalfen.projects.crypto.bouncycastle.openpgp.keys.keyrings.KeyringConfigs;
|
import name.neuhalfen.projects.crypto.bouncycastle.openpgp.keys.keyrings.KeyringConfigs;
|
||||||
import org.bouncycastle.openpgp.PGPException;
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
|
import org.bouncycastle.openpgp.PGPKeyRingGenerator;
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||||
|
import org.bouncycastle.openpgp.PGPSecretKey;
|
||||||
import org.bouncycastle.util.io.Streams;
|
import org.bouncycastle.util.io.Streams;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.jxmpp.jid.impl.JidCreate;
|
||||||
|
|
||||||
public class BasicEncryptionTest extends SmackTestSuite {
|
public class BasicEncryptionTest extends SmackTestSuite {
|
||||||
|
|
||||||
|
@ -55,7 +59,7 @@ public class BasicEncryptionTest extends SmackTestSuite {
|
||||||
public BasicEncryptionTest() throws IOException, PGPException {
|
public BasicEncryptionTest() throws IOException, PGPException {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
// Prepare Juliets keyring
|
// Prepare Juliet's keyring
|
||||||
keyringJuliet = KeyringConfigs.forGpgExportedKeys(KeyringConfigCallbacks.withUnprotectedKeys());
|
keyringJuliet = KeyringConfigs.forGpgExportedKeys(KeyringConfigCallbacks.withUnprotectedKeys());
|
||||||
((InMemoryKeyring) keyringJuliet).addSecretKey(TestKeys.JULIET_PRIV.getBytes(UTF8));
|
((InMemoryKeyring) keyringJuliet).addSecretKey(TestKeys.JULIET_PRIV.getBytes(UTF8));
|
||||||
((InMemoryKeyring) keyringJuliet).addPublicKey(TestKeys.JULIET_PUB.getBytes(UTF8));
|
((InMemoryKeyring) keyringJuliet).addPublicKey(TestKeys.JULIET_PUB.getBytes(UTF8));
|
||||||
|
@ -71,12 +75,10 @@ public class BasicEncryptionTest extends SmackTestSuite {
|
||||||
@Test
|
@Test
|
||||||
public void encryptionTest()
|
public void encryptionTest()
|
||||||
throws IOException, PGPException, NoSuchAlgorithmException, SignatureException, NoSuchProviderException {
|
throws IOException, PGPException, NoSuchAlgorithmException, SignatureException, NoSuchProviderException {
|
||||||
|
|
||||||
|
|
||||||
ByteArrayOutputStream result = new ByteArrayOutputStream();
|
ByteArrayOutputStream result = new ByteArrayOutputStream();
|
||||||
KeySelectionStrategy selectionStrategy = new Xep0373KeySelectionStrategy(new Date());
|
KeySelectionStrategy selectionStrategy = new XmppKeySelectionStrategy(new Date());
|
||||||
|
|
||||||
byte[] message = "Hello World!!!!".getBytes(UTF8);
|
byte[] message = "How long do you want these messages to remain secret?".getBytes(UTF8);
|
||||||
|
|
||||||
// Encrypt
|
// Encrypt
|
||||||
OutputStream out = BouncyGPG.encryptToStream()
|
OutputStream out = BouncyGPG.encryptToStream()
|
||||||
|
@ -108,4 +110,56 @@ public class BasicEncryptionTest extends SmackTestSuite {
|
||||||
|
|
||||||
assertTrue(Arrays.equals(message, message2));
|
assertTrue(Arrays.equals(message, message2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void encryptionWithFreshKeysTest()
|
||||||
|
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);
|
||||||
|
PGPSecretKey s1 = g1.generateSecretKeyRing().getSecretKey();
|
||||||
|
PGPSecretKey s2 = g2.generateSecretKeyRing().getSecretKey();
|
||||||
|
PGPPublicKey p1 = g1.generatePublicKeyRing().getPublicKey();
|
||||||
|
PGPPublicKey p2 = g2.generatePublicKeyRing().getPublicKey();
|
||||||
|
|
||||||
|
KeyringConfig c1 = KeyringConfigs.forGpgExportedKeys(KeyringConfigCallbacks.withUnprotectedKeys());
|
||||||
|
KeyringConfig c2 = KeyringConfigs.forGpgExportedKeys(KeyringConfigCallbacks.withUnprotectedKeys());
|
||||||
|
((InMemoryKeyring) c1).addSecretKey(s1.getEncoded());
|
||||||
|
((InMemoryKeyring) c1).addPublicKey(p1.getEncoded());
|
||||||
|
((InMemoryKeyring) c1).addPublicKey(p2.getEncoded());
|
||||||
|
((InMemoryKeyring) c2).addSecretKey(s2.getEncoded());
|
||||||
|
((InMemoryKeyring) c2).addPublicKey(p2.getEncoded());
|
||||||
|
((InMemoryKeyring) c2).addPublicKey(p1.getEncoded());
|
||||||
|
|
||||||
|
ByteArrayOutputStream result = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
byte[] m1 = "I want them to remain secret for as long as men are capable of evil.".getBytes(UTF8);
|
||||||
|
OutputStream encrypt = BouncyGPG.encryptToStream()
|
||||||
|
.withConfig(c1)
|
||||||
|
.withKeySelectionStrategy(new XmppKeySelectionStrategy(new Date()))
|
||||||
|
.withOxAlgorithms()
|
||||||
|
.toRecipients("xmpp:" + alice, "xmpp:" + bob)
|
||||||
|
.andSignWith("xmpp:" + alice)
|
||||||
|
.binaryOutput()
|
||||||
|
.andWriteTo(result);
|
||||||
|
|
||||||
|
encrypt.write(m1);
|
||||||
|
encrypt.close();
|
||||||
|
|
||||||
|
byte[] e1 = result.toByteArray();
|
||||||
|
result.reset();
|
||||||
|
|
||||||
|
ByteArrayInputStream in = new ByteArrayInputStream(e1);
|
||||||
|
InputStream decrypt = BouncyGPG.decryptAndVerifyStream()
|
||||||
|
.withConfig(c2)
|
||||||
|
.withKeySelectionStrategy(new XmppKeySelectionStrategy(new Date()))
|
||||||
|
.andValidateSomeoneSigned()
|
||||||
|
.fromEncryptedInputStream(in);
|
||||||
|
|
||||||
|
Streams.pipeAll(decrypt, result);
|
||||||
|
|
||||||
|
byte[] m2 = result.toByteArray();
|
||||||
|
assertTrue(Arrays.equals(m1, m2));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Copyright 2018 Paul Schaub.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.jivesoftware.smackx.ox.bouncycastle;
|
||||||
|
|
||||||
|
import static junit.framework.TestCase.assertTrue;
|
||||||
|
import static org.custommonkey.xmlunit.XMLAssert.assertXMLEqual;
|
||||||
|
|
||||||
|
import java.security.Security;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.packet.ExtensionElement;
|
||||||
|
import org.jivesoftware.smack.packet.Message;
|
||||||
|
import org.jivesoftware.smack.test.util.SmackTestSuite;
|
||||||
|
import org.jivesoftware.smackx.ox.OpenPgpMessage;
|
||||||
|
import org.jivesoftware.smackx.ox.element.OpenPgpContentElement;
|
||||||
|
import org.jivesoftware.smackx.ox.element.OpenPgpElement;
|
||||||
|
import org.jivesoftware.smackx.ox.element.PubkeyElement;
|
||||||
|
import org.jivesoftware.smackx.ox.element.SigncryptElement;
|
||||||
|
|
||||||
|
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.jxmpp.jid.BareJid;
|
||||||
|
import org.jxmpp.jid.Jid;
|
||||||
|
import org.jxmpp.jid.impl.JidCreate;
|
||||||
|
|
||||||
|
public class BouncycastleOpenPgpProviderTest extends SmackTestSuite {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void encryptAndSign_decryptAndVerifyElementTest() throws Exception {
|
||||||
|
Security.addProvider(new BouncyCastleProvider());
|
||||||
|
|
||||||
|
// Create providers for alice and the cat
|
||||||
|
BareJid alice = JidCreate.bareFrom("alice@wonderland.lit");
|
||||||
|
BareJid cheshire = JidCreate.bareFrom("cheshire@wonderland.lit");
|
||||||
|
BouncycastleOpenPgpProvider aliceProvider = new BouncycastleOpenPgpProvider(alice);
|
||||||
|
BouncycastleOpenPgpProvider cheshireProvider = new BouncycastleOpenPgpProvider(cheshire);
|
||||||
|
|
||||||
|
// dry exchange keys
|
||||||
|
PubkeyElement aliceKeys = aliceProvider.createPubkeyElement();
|
||||||
|
PubkeyElement cheshireKeys = cheshireProvider.createPubkeyElement();
|
||||||
|
aliceProvider.processRecipientsPublicKey(cheshire, cheshireKeys);
|
||||||
|
cheshireProvider.processRecipientsPublicKey(alice, aliceKeys);
|
||||||
|
|
||||||
|
SigncryptElement signcryptElement = new SigncryptElement(
|
||||||
|
Collections.<Jid>singleton(cheshire),
|
||||||
|
Collections.<ExtensionElement>singletonList(
|
||||||
|
new Message.Body("en", "How do you know I’m mad?")));
|
||||||
|
OpenPgpElement encrypted = aliceProvider.signAndEncrypt(
|
||||||
|
signcryptElement.toInputStream(),
|
||||||
|
Collections.singleton(cheshire));
|
||||||
|
|
||||||
|
OpenPgpMessage decrypted = cheshireProvider.decryptAndVerify(encrypted, alice);
|
||||||
|
OpenPgpContentElement content = decrypted.getOpenPgpContentElement();
|
||||||
|
assertTrue(content instanceof SigncryptElement);
|
||||||
|
|
||||||
|
assertXMLEqual(signcryptElement.toXML(null).toString(), content.toXML(null).toString());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Copyright 2018 Paul Schaub.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.jivesoftware.smackx.ox.bouncycastle;
|
||||||
|
|
||||||
|
import static junit.framework.TestCase.assertEquals;
|
||||||
|
import static junit.framework.TestCase.assertTrue;
|
||||||
|
|
||||||
|
import name.neuhalfen.projects.crypto.bouncycastle.openpgp.algorithms.PublicKeyType;
|
||||||
|
import org.bouncycastle.openpgp.PGPSecretKey;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.jxmpp.jid.impl.JidCreate;
|
||||||
|
|
||||||
|
public class KeyGenerationTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createSecretKey() throws Exception {
|
||||||
|
PGPSecretKey secretKey = BouncycastleOpenPgpProvider
|
||||||
|
.generateKey(JidCreate.bareFrom("alice@wonderland.lit"), null)
|
||||||
|
.generateSecretKeyRing()
|
||||||
|
.getSecretKey();
|
||||||
|
assertEquals(PublicKeyType.RSA_GENERAL.getId(), secretKey.getPublicKey().getAlgorithm());
|
||||||
|
assertTrue(secretKey.getPublicKey().isEncryptionKey());
|
||||||
|
assertEquals(2048, secretKey.getPublicKey().getBitStrength());
|
||||||
|
assertEquals("xmpp:alice@wonderland.lit", secretKey.getPublicKey().getUserIDs().next());
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +0,0 @@
|
||||||
package org.jivesoftware.smackx.ox;
|
|
||||||
|
|
||||||
public class OpenPgpKeyStore {
|
|
||||||
|
|
||||||
public OpenPgpKeyStore() {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -36,7 +36,7 @@ public class OpenPgpMessage {
|
||||||
}
|
}
|
||||||
|
|
||||||
private final String element;
|
private final String element;
|
||||||
private final State state;
|
private State state;
|
||||||
|
|
||||||
private OpenPgpContentElement openPgpContentElement;
|
private OpenPgpContentElement openPgpContentElement;
|
||||||
|
|
||||||
|
@ -56,6 +56,16 @@ public class OpenPgpMessage {
|
||||||
return;
|
return;
|
||||||
|
|
||||||
openPgpContentElement = OpenPgpContentElementProvider.parseOpenPgpContentElement(element);
|
openPgpContentElement = OpenPgpContentElementProvider.parseOpenPgpContentElement(element);
|
||||||
|
if (state == null) {
|
||||||
|
if (openPgpContentElement instanceof SigncryptElement) {
|
||||||
|
state = State.signcrypt;
|
||||||
|
} else if (openPgpContentElement instanceof SignElement) {
|
||||||
|
state = State.sign;
|
||||||
|
} else {
|
||||||
|
state = State.crypt;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case signcrypt:
|
case signcrypt:
|
||||||
if (!(openPgpContentElement instanceof SigncryptElement)) {
|
if (!(openPgpContentElement instanceof SigncryptElement)) {
|
||||||
|
|
|
@ -17,9 +17,15 @@
|
||||||
package org.jivesoftware.smackx.ox;
|
package org.jivesoftware.smackx.ox;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.jivesoftware.smackx.ox.element.OpenPgpElement;
|
||||||
|
|
||||||
|
import org.jxmpp.jid.BareJid;
|
||||||
|
|
||||||
public interface OpenPgpProvider {
|
public interface OpenPgpProvider {
|
||||||
|
|
||||||
OpenPgpMessage toOpenPgpMessage(InputStream is);
|
OpenPgpMessage decryptAndVerify(OpenPgpElement element, BareJid sender) throws Exception;
|
||||||
|
|
||||||
|
OpenPgpElement signAndEncrypt(InputStream inputStream, Set<BareJid> recipients) throws Exception;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,9 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.ox.element;
|
package org.jivesoftware.smackx.ox.element;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -99,4 +102,9 @@ public abstract class OpenPgpContentElement implements ExtensionElement {
|
||||||
}
|
}
|
||||||
xml.closeElement(ELEM_PAYLOAD);
|
xml.closeElement(ELEM_PAYLOAD);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public InputStream toInputStream() {
|
||||||
|
byte[] encoded = toXML(null).toString().getBytes(Charset.forName("UTF-8"));
|
||||||
|
return new ByteArrayInputStream(encoded);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||||
import org.jivesoftware.smack.util.stringencoder.Base64;
|
import org.jivesoftware.smack.util.stringencoder.Base64;
|
||||||
import org.jivesoftware.smackx.ox.OpenPgpManager;
|
import org.jivesoftware.smackx.ox.OpenPgpManager;
|
||||||
import org.jivesoftware.smackx.ox.OpenPgpMessage;
|
import org.jivesoftware.smackx.ox.OpenPgpMessage;
|
||||||
|
import org.jivesoftware.smackx.ox.OpenPgpProvider;
|
||||||
|
|
||||||
import org.xmlpull.v1.XmlPullParserException;
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
|
|
||||||
|
@ -47,7 +48,7 @@ public class OpenPgpElement implements ExtensionElement {
|
||||||
this.base64EncodedOpenPgpMessage = base64EncodedOpenPgpMessage;
|
this.base64EncodedOpenPgpMessage = base64EncodedOpenPgpMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
public OpenPgpMessage getOpenPgpMessage() {
|
public OpenPgpMessage getOpenPgpMessage(OpenPgpProvider provider) {
|
||||||
if (openPgpMessage == null) {
|
if (openPgpMessage == null) {
|
||||||
ensureOpenPgpMessageBytesSet();
|
ensureOpenPgpMessageBytesSet();
|
||||||
InputStream is = new ByteArrayInputStream(openPgpMessageBytes);
|
InputStream is = new ByteArrayInputStream(openPgpMessageBytes);
|
||||||
|
@ -57,14 +58,18 @@ public class OpenPgpElement implements ExtensionElement {
|
||||||
return openPgpMessage;
|
return openPgpMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
public OpenPgpContentElement getContentElement() throws XmlPullParserException, IOException {
|
public OpenPgpContentElement getContentElement(OpenPgpProvider provider) throws XmlPullParserException, IOException {
|
||||||
if (openPgpContentElement == null) {
|
if (openPgpContentElement == null) {
|
||||||
openPgpContentElement = getOpenPgpMessage().getOpenPgpContentElement();
|
openPgpContentElement = getOpenPgpMessage(provider).getOpenPgpContentElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
return openPgpContentElement;
|
return openPgpContentElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getEncryptedBase64MessageContent() {
|
||||||
|
return base64EncodedOpenPgpMessage;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getElementName() {
|
public String getElementName() {
|
||||||
return ELEMENT;
|
return ELEMENT;
|
||||||
|
|
|
@ -55,9 +55,20 @@ public abstract class OpenPgpContentElementProvider<O extends OpenPgpContentElem
|
||||||
}
|
}
|
||||||
|
|
||||||
public static OpenPgpContentElement parseOpenPgpContentElement(XmlPullParser parser)
|
public static OpenPgpContentElement parseOpenPgpContentElement(XmlPullParser parser)
|
||||||
throws IOException, XmlPullParserException {
|
throws XmlPullParserException, IOException {
|
||||||
// TODO
|
try {
|
||||||
return null;
|
switch (parser.getName()) {
|
||||||
|
case SigncryptElement.ELEMENT:
|
||||||
|
return SigncryptElementProvider.TEST_INSTANCE.parse(parser);
|
||||||
|
case SignElement.ELEMENT:
|
||||||
|
return SignElementProvider.TEST_INSTANCE.parse(parser);
|
||||||
|
case CryptElement.ELEMENT:
|
||||||
|
return CryptElementProvider.TEST_INSTANCE.parse(parser);
|
||||||
|
default: return null;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new XmlPullParserException(e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
Loading…
Reference in a new issue