mirror of
https://github.com/vanitasvitae/Smack.git
synced 2024-12-25 03:57:58 +01:00
WiP, please don't look
This commit is contained in:
parent
dab342e97e
commit
49a51bfa2d
32 changed files with 996 additions and 1053 deletions
|
@ -21,6 +21,6 @@
|
|||
<className>org.jivesoftware.smack.java7.Java7SmackInitializer</className>
|
||||
<className>org.jivesoftware.smack.im.SmackImInitializer</className>
|
||||
<className>org.jivesoftware.smackx.omemo.OmemoInitializer</className>
|
||||
<className>org.jivesoftware.smackx.ox.OpenPgpInitializer</className>
|
||||
<className>org.jivesoftware.smackx.ox.util.OpenPgpInitializer</className>
|
||||
</optionalStartupClasses>
|
||||
</smack>
|
||||
|
|
|
@ -7,7 +7,7 @@ dependencies {
|
|||
compile project(':smack-core')
|
||||
compile project(':smack-openpgp')
|
||||
compile 'org.bouncycastle:bcpg-jdk15on:1.59'
|
||||
compile 'de.vanitasvitae.pgpainless:pgpainless:2.1.0'
|
||||
compile 'de.vanitasvitae.crypto:pgpainless:0.1-SNAPSHOT'
|
||||
testCompile project(path: ":smack-core", configuration: "testRuntime")
|
||||
testCompile project(path: ":smack-core", configuration: "archives")
|
||||
testCompile project(path: ":smack-openpgp", configuration: "testRuntime")
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
package org.jivesoftware.smackx.ox.bouncycastle;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
|
||||
import org.jxmpp.jid.BareJid;
|
||||
|
||||
public abstract class AbstractPainlessOpenPgpStore implements PainlessOpenPgpStore {
|
||||
|
||||
private final BcKeyFingerprintCalculator fingerprintCalculator = new BcKeyFingerprintCalculator();
|
||||
|
||||
private final Map<BareJid, PGPPublicKeyRingCollection> publicKeyRings = new HashMap<>();
|
||||
private final Map<BareJid, PGPSecretKeyRingCollection> secretKeyRings = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public PGPPublicKeyRingCollection getPublicKeyRings(BareJid owner) throws IOException, PGPException {
|
||||
PGPPublicKeyRingCollection keyRing = publicKeyRings.get(owner);
|
||||
if (keyRing != null) {
|
||||
return keyRing;
|
||||
}
|
||||
|
||||
byte[] bytes = loadPublicKeyRingBytes(owner);
|
||||
keyRing = new PGPPublicKeyRingCollection(bytes, fingerprintCalculator);
|
||||
|
||||
publicKeyRings.put(owner, keyRing);
|
||||
|
||||
return keyRing;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PGPSecretKeyRingCollection getSecretKeyRing(BareJid owner) throws IOException, PGPException {
|
||||
PGPSecretKeyRingCollection keyRing = secretKeyRings.get(owner);
|
||||
if (keyRing != null) {
|
||||
return keyRing;
|
||||
}
|
||||
|
||||
byte[] bytes = loadSecretKeyRingBytes(owner);
|
||||
keyRing = new PGPSecretKeyRingCollection(bytes, fingerprintCalculator);
|
||||
|
||||
secretKeyRings.put(owner, keyRing);
|
||||
|
||||
return keyRing;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storePublicKeyRing(BareJid owner, PGPPublicKeyRingCollection publicKeys) throws IOException {
|
||||
publicKeyRings.put(owner, publicKeys);
|
||||
storePublicKeyRingBytes(owner, publicKeys.getEncoded());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storeSecretKeyRing(BareJid owner, PGPSecretKeyRingCollection secretKeys) throws IOException {
|
||||
secretKeyRings.put(owner, secretKeys);
|
||||
storeSecretKeyRingBytes(owner, secretKeys.getEncoded());
|
||||
}
|
||||
|
||||
}
|
|
@ -1,231 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* 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 java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.util.Date;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jivesoftware.smack.XMPPConnection;
|
||||
import org.jivesoftware.smack.util.stringencoder.Base64;
|
||||
import org.jivesoftware.smackx.ox.OpenPgpMessage;
|
||||
import org.jivesoftware.smackx.ox.OpenPgpProvider;
|
||||
import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint;
|
||||
import org.jivesoftware.smackx.ox.callback.SecretKeyRestoreSelectionCallback;
|
||||
import org.jivesoftware.smackx.ox.element.CryptElement;
|
||||
import org.jivesoftware.smackx.ox.element.OpenPgpElement;
|
||||
import org.jivesoftware.smackx.ox.element.PubkeyElement;
|
||||
import org.jivesoftware.smackx.ox.element.PublicKeysListElement;
|
||||
import org.jivesoftware.smackx.ox.element.SecretkeyElement;
|
||||
import org.jivesoftware.smackx.ox.element.SignElement;
|
||||
import org.jivesoftware.smackx.ox.element.SigncryptElement;
|
||||
import org.jivesoftware.smackx.ox.exception.InvalidBackupCodeException;
|
||||
import org.jivesoftware.smackx.ox.exception.MissingOpenPgpKeyPairException;
|
||||
import org.jivesoftware.smackx.ox.exception.MissingOpenPgpPublicKeyException;
|
||||
import org.jivesoftware.smackx.ox.exception.SmackOpenPgpException;
|
||||
|
||||
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.XmppKeySelectionStrategy;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPKeyRingGenerator;
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
import org.bouncycastle.util.io.Streams;
|
||||
import org.jxmpp.jid.BareJid;
|
||||
|
||||
public class BCOpenPgpProvider implements OpenPgpProvider {
|
||||
|
||||
private final BareJid user;
|
||||
|
||||
private BCOpenPgpStore store;
|
||||
|
||||
|
||||
public BCOpenPgpProvider(BareJid user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public void setStore(BCOpenPgpStore store) {
|
||||
this.store = store;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OpenPgpV4Fingerprint primaryOpenPgpKeyPairFingerprint() {
|
||||
return store.primaryOpenPgpKeyPairFingerprint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<OpenPgpV4Fingerprint> availableOpenPgpKeyPairFingerprints() {
|
||||
return store.availableOpenPgpKeyPairFingerprints();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<OpenPgpV4Fingerprint> announcedOpenPgpKeyFingerprints(BareJid contact) {
|
||||
return store.announcedOpenPgpKeyFingerprints(contact);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OpenPgpElement signAndEncrypt(SigncryptElement element, OpenPgpV4Fingerprint signingKey, Set<OpenPgpV4Fingerprint> encryptionKeys)
|
||||
throws MissingOpenPgpKeyPairException, MissingOpenPgpPublicKeyException {
|
||||
if (encryptionKeys.isEmpty()) {
|
||||
throw new IllegalArgumentException("Set of recipients must not be empty");
|
||||
}
|
||||
|
||||
encryptionKeys.addAll(store.announcedOpenPgpKeyFingerprints(user));
|
||||
long[] recipientIds = new long[encryptionKeys.size()];
|
||||
int pos = 0;
|
||||
for (OpenPgpV4Fingerprint f : encryptionKeys) {
|
||||
recipientIds[pos++] = f.getKeyId();
|
||||
}
|
||||
|
||||
InputStream inputStream = element.toInputStream();
|
||||
ByteArrayOutputStream encryptedOut = new ByteArrayOutputStream();
|
||||
|
||||
try {
|
||||
OutputStream encryptor = BouncyGPG.encryptToStream()
|
||||
.withConfig(store.getKeyringConfig())
|
||||
.withKeySelectionStrategy(new XmppKeySelectionStrategy(new Date()))
|
||||
.withOxAlgorithms()
|
||||
.toRecipients(recipientIds)
|
||||
.andSignWith(signingKey.getKeyId())
|
||||
.binaryOutput()
|
||||
.andWriteTo(encryptedOut);
|
||||
|
||||
Streams.pipeAll(inputStream, encryptor);
|
||||
encryptor.close();
|
||||
|
||||
String base64 = Base64.encodeToString(encryptedOut.toByteArray());
|
||||
|
||||
return new OpenPgpElement(base64);
|
||||
} catch (Exception e) {
|
||||
throw new AssertionError(e);
|
||||
// TODO: Later
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public OpenPgpMessage decryptAndVerify(OpenPgpElement element, Set<OpenPgpV4Fingerprint> sendersKeys)
|
||||
throws MissingOpenPgpKeyPairException, MissingOpenPgpPublicKeyException {
|
||||
|
||||
ByteArrayInputStream encryptedIn = new ByteArrayInputStream(
|
||||
element.getEncryptedBase64MessageContent().getBytes(Charset.forName("UTF-8")));
|
||||
|
||||
try {
|
||||
InputStream decrypted = BouncyGPG.decryptAndVerifyStream()
|
||||
.withConfig(store.getKeyringConfig())
|
||||
.withKeySelectionStrategy(new XmppKeySelectionStrategy(new Date()))
|
||||
.andValidateSomeoneSigned() // TODO: Validate using sender keys
|
||||
.fromEncryptedInputStream(encryptedIn);
|
||||
|
||||
ByteArrayOutputStream decryptedOut = new ByteArrayOutputStream();
|
||||
|
||||
Streams.pipeAll(decrypted, decryptedOut);
|
||||
|
||||
return new OpenPgpMessage(OpenPgpMessage.State.signcrypt, new String(decryptedOut.toByteArray(), Charset.forName("UTF-8")));
|
||||
} catch (IOException | NoSuchProviderException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public OpenPgpElement sign(SignElement element, OpenPgpV4Fingerprint singingKeyFingerprint)
|
||||
throws MissingOpenPgpKeyPairException {
|
||||
throw new AssertionError("Feature not implemented!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public OpenPgpMessage verify(OpenPgpElement element, Set<OpenPgpV4Fingerprint> singingKeyFingerprints)
|
||||
throws MissingOpenPgpPublicKeyException {
|
||||
throw new AssertionError("Feature not implemented!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public OpenPgpElement encrypt(CryptElement element, Set<OpenPgpV4Fingerprint> encryptionKeyFingerprints)
|
||||
throws MissingOpenPgpPublicKeyException {
|
||||
throw new AssertionError("Feature not implemented!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public OpenPgpMessage decrypt(OpenPgpElement element)
|
||||
throws MissingOpenPgpKeyPairException {
|
||||
throw new AssertionError("Feature not implemented!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public PubkeyElement createPubkeyElement(OpenPgpV4Fingerprint fingerprint)
|
||||
throws MissingOpenPgpPublicKeyException, SmackOpenPgpException {
|
||||
return store.createPubkeyElement(fingerprint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storePublicKey(BareJid owner, OpenPgpV4Fingerprint fingerprint, PubkeyElement element)
|
||||
throws SmackOpenPgpException {
|
||||
store.storePublicKey(owner, fingerprint, element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storePublicKeysList(XMPPConnection connection, PublicKeysListElement listElement, BareJid owner) {
|
||||
store.storePublicKeysList(connection, listElement, owner);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OpenPgpV4Fingerprint createOpenPgpKeyPair()
|
||||
throws NoSuchAlgorithmException, NoSuchProviderException, SmackOpenPgpException {
|
||||
return store.createOpenPgpKeyPair();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecretkeyElement createSecretkeyElement(Set<OpenPgpV4Fingerprint> fingerprints, String password)
|
||||
throws MissingOpenPgpKeyPairException, SmackOpenPgpException {
|
||||
return store.createSecretkeyElement(fingerprints, password);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<OpenPgpV4Fingerprint> availableOpenPgpPublicKeysFingerprints(BareJid contact)
|
||||
throws SmackOpenPgpException {
|
||||
return store.availableOpenPgpPublicKeysFingerprints(contact);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void restoreSecretKeyBackup(SecretkeyElement secretkeyElement, String password, SecretKeyRestoreSelectionCallback callback)
|
||||
throws SmackOpenPgpException, InvalidBackupCodeException {
|
||||
store.restoreSecretKeyBackup(secretkeyElement, password, callback);
|
||||
}
|
||||
|
||||
static PGPKeyRingGenerator generateKey(BareJid owner)
|
||||
throws NoSuchAlgorithmException, PGPException, NoSuchProviderException {
|
||||
PGPKeyRingGenerator generator = BouncyGPG.createKeyPair()
|
||||
.withRSAKeys()
|
||||
.ofSize(PublicKeySize.RSA._2048)
|
||||
.forIdentity("xmpp:" + owner.toString())
|
||||
.withoutPassphrase()
|
||||
.build();
|
||||
return generator;
|
||||
}
|
||||
|
||||
public static OpenPgpV4Fingerprint getFingerprint(PGPPublicKey publicKey) {
|
||||
byte[] hex = Hex.encode(publicKey.getFingerprint());
|
||||
return new OpenPgpV4Fingerprint(hex);
|
||||
}
|
||||
}
|
|
@ -1,567 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* 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 java.io.BufferedOutputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.text.ParseException;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.jivesoftware.smack.XMPPConnection;
|
||||
import org.jivesoftware.smack.util.stringencoder.Base64;
|
||||
import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint;
|
||||
import org.jivesoftware.smackx.ox.Util;
|
||||
import org.jivesoftware.smackx.ox.callback.SecretKeyRestoreSelectionCallback;
|
||||
import org.jivesoftware.smackx.ox.element.PubkeyElement;
|
||||
import org.jivesoftware.smackx.ox.element.PublicKeysListElement;
|
||||
import org.jivesoftware.smackx.ox.element.SecretkeyElement;
|
||||
import org.jivesoftware.smackx.ox.exception.InvalidBackupCodeException;
|
||||
import org.jivesoftware.smackx.ox.exception.MissingOpenPgpKeyPairException;
|
||||
import org.jivesoftware.smackx.ox.exception.MissingOpenPgpPublicKeyException;
|
||||
import org.jivesoftware.smackx.ox.exception.SmackOpenPgpException;
|
||||
|
||||
import name.neuhalfen.projects.crypto.bouncycastle.openpgp.algorithms.PGPHashAlgorithms;
|
||||
import name.neuhalfen.projects.crypto.bouncycastle.openpgp.algorithms.PGPSymmetricEncryptionAlgorithms;
|
||||
import name.neuhalfen.projects.crypto.bouncycastle.openpgp.keys.callbacks.KeyringConfigCallback;
|
||||
import name.neuhalfen.projects.crypto.bouncycastle.openpgp.keys.callbacks.KeyringConfigCallbacks;
|
||||
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.KeyringConfigs;
|
||||
import org.bouncycastle.bcpg.HashAlgorithmTags;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPrivateKey;
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor;
|
||||
import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
|
||||
import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider;
|
||||
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
|
||||
import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
|
||||
import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder;
|
||||
import org.jxmpp.jid.BareJid;
|
||||
import org.jxmpp.util.XmppDateTime;
|
||||
|
||||
public class FileBasedBcOpenPgpStore implements BCOpenPgpStore {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(FileBasedBcOpenPgpStore.class.getName());
|
||||
|
||||
private final File basePath;
|
||||
private final BareJid user;
|
||||
private final InMemoryKeyring keyringConfig;
|
||||
private final KeyringConfigCallback configCallback;
|
||||
private OpenPgpV4Fingerprint primaryKeyFingerprint;
|
||||
|
||||
public FileBasedBcOpenPgpStore(File basePath, BareJid user, KeyringConfigCallback passwordCallback)
|
||||
throws IOException, PGPException {
|
||||
this.basePath = basePath;
|
||||
this.user = user;
|
||||
|
||||
File pub = publicKeyringPath();
|
||||
if (!pub.exists()) {
|
||||
pub.getParentFile().mkdirs();
|
||||
pub.createNewFile();
|
||||
}
|
||||
|
||||
File sec = secretKeyringPath();
|
||||
if (!sec.exists()) {
|
||||
sec.createNewFile();
|
||||
}
|
||||
|
||||
configCallback = passwordCallback;
|
||||
keyringConfig = KeyringConfigs.forGpgExportedKeys(configCallback);
|
||||
|
||||
addPublicKeysFromFile(keyringConfig, pub, configCallback);
|
||||
PGPPublicKey lastAdded = addSecretKeysFromFile(keyringConfig, sec, configCallback);
|
||||
|
||||
if (lastAdded != null) {
|
||||
primaryKeyFingerprint = BCOpenPgpProvider.getFingerprint(lastAdded);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public OpenPgpV4Fingerprint primaryOpenPgpKeyPairFingerprint() {
|
||||
return primaryKeyFingerprint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<OpenPgpV4Fingerprint> availableOpenPgpKeyPairFingerprints() {
|
||||
Set<OpenPgpV4Fingerprint> availableKeyPairs = new HashSet<>();
|
||||
try {
|
||||
for (PGPSecretKeyRing secRing : keyringConfig.getSecretKeyRings()) {
|
||||
for (PGPSecretKey secKey : secRing) {
|
||||
availableKeyPairs.add(BCOpenPgpProvider.getFingerprint(secKey.getPublicKey()));
|
||||
}
|
||||
}
|
||||
} catch (IOException | PGPException e) {
|
||||
LOGGER.log(Level.SEVERE, "Error going through available key pairs.", e);
|
||||
}
|
||||
return availableKeyPairs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<OpenPgpV4Fingerprint, Date> announcedOpenPgpKeyFingerprints(BareJid contact) {
|
||||
Map<OpenPgpV4Fingerprint, Date> announcedKeys = new HashMap<>();
|
||||
File listPath = contactsList(contact);
|
||||
if (listPath.exists() && listPath.isFile()) {
|
||||
BufferedReader reader = null;
|
||||
try {
|
||||
reader = new BufferedReader(new InputStreamReader(
|
||||
new FileInputStream(listPath), "UTF8"));
|
||||
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
line = line.trim();
|
||||
if (line.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String[] split = line.split(" ");
|
||||
|
||||
OpenPgpV4Fingerprint fingerprint;
|
||||
Date date = null;
|
||||
try {
|
||||
fingerprint = new OpenPgpV4Fingerprint(split[0]);
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOGGER.log(Level.INFO, "Skip malformed fingerprint " + line + " of " + contact.toString());
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
if (split.length > 1)
|
||||
date = XmppDateTime.parseXEP0082Date(split[1]);
|
||||
}
|
||||
catch (ParseException e) {
|
||||
LOGGER.log(Level.WARNING, "Could not parse date", e);
|
||||
}
|
||||
announcedKeys.put(fingerprint, date);
|
||||
}
|
||||
reader.close();
|
||||
} catch (IOException e) {
|
||||
if (reader != null) {
|
||||
try {
|
||||
reader.close();
|
||||
} catch (IOException e1) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return announcedKeys;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<OpenPgpV4Fingerprint> availableOpenPgpPublicKeysFingerprints(BareJid contact)
|
||||
throws SmackOpenPgpException {
|
||||
Set<OpenPgpV4Fingerprint> availableKeys = new HashSet<>();
|
||||
try {
|
||||
Iterator<PGPPublicKeyRing> ringIterator = keyringConfig.getPublicKeyRings().getKeyRings("xmpp:" + contact.toString());
|
||||
while (ringIterator.hasNext()) {
|
||||
PGPPublicKeyRing ring = ringIterator.next();
|
||||
Iterator<PGPPublicKey> keyIterator = ring.getPublicKeys();
|
||||
while (keyIterator.hasNext()) {
|
||||
PGPPublicKey key = keyIterator.next();
|
||||
if (key.isEncryptionKey()) {
|
||||
availableKeys.add(BCOpenPgpProvider.getFingerprint(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (PGPException | IOException e) {
|
||||
throw new SmackOpenPgpException(e);
|
||||
}
|
||||
return availableKeys;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storePublicKeysList(XMPPConnection connection, PublicKeysListElement listElement, BareJid owner) {
|
||||
File listPath = contactsList(owner);
|
||||
try {
|
||||
if (!listPath.exists()) {
|
||||
listPath.getParentFile().mkdirs();
|
||||
listPath.createNewFile();
|
||||
BufferedWriter writer = null;
|
||||
try {
|
||||
writer = new BufferedWriter(new OutputStreamWriter(
|
||||
new FileOutputStream(listPath), "UTF8"));
|
||||
|
||||
for (PublicKeysListElement.PubkeyMetadataElement entry : listElement.getMetadata().values()) {
|
||||
OpenPgpV4Fingerprint fingerprint = entry.getV4Fingerprint();
|
||||
Date date = entry.getDate();
|
||||
String line = fingerprint.toString() + (date != null ? date : "");
|
||||
writer.write(line);
|
||||
writer.newLine();
|
||||
}
|
||||
|
||||
writer.close();
|
||||
|
||||
} catch (IOException e) {
|
||||
if (writer != null) {
|
||||
writer.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOGGER.log(Level.WARNING, "Error writing list of announced keys for " + owner.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PubkeyElement createPubkeyElement(OpenPgpV4Fingerprint fingerprint)
|
||||
throws MissingOpenPgpPublicKeyException, SmackOpenPgpException {
|
||||
try {
|
||||
PGPPublicKey publicKey = keyringConfig.getPublicKeyRings().getPublicKey(Util.keyIdFromFingerprint(fingerprint));
|
||||
if (publicKey == null) {
|
||||
throw new MissingOpenPgpPublicKeyException(user, fingerprint);
|
||||
}
|
||||
byte[] base64 = Base64.encode(publicKey.getEncoded());
|
||||
return new PubkeyElement(new PubkeyElement.PubkeyDataElement(base64), new Date());
|
||||
} catch (PGPException | IOException e) {
|
||||
throw new SmackOpenPgpException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storePublicKey(BareJid owner, OpenPgpV4Fingerprint fingerprint, PubkeyElement element, Date latestMetadataDate)
|
||||
throws SmackOpenPgpException {
|
||||
byte[] base64decoded = Base64.decode(element.getDataElement().getB64Data());
|
||||
try {
|
||||
keyringConfig.addPublicKey(base64decoded);
|
||||
writePublicKeysToFile(keyringConfig, publicKeyringPath());
|
||||
} catch (PGPException | IOException e) {
|
||||
throw new SmackOpenPgpException(e);
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOGGER.log(Level.WARNING, "Public Key with ID " + fingerprint.toString() + " of " +
|
||||
owner + " is already in memory. Skip.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
writeDateToFile(publicKeyUpdateDatePath(owner, fingerprint), latestMetadataDate);
|
||||
} catch (IOException e) {
|
||||
LOGGER.log(Level.WARNING, "Could not store update date for " + fingerprint.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecretkeyElement createSecretkeyElement(Set<OpenPgpV4Fingerprint> fingerprints, String password)
|
||||
throws MissingOpenPgpKeyPairException, SmackOpenPgpException {
|
||||
|
||||
PGPDigestCalculator calculator;
|
||||
try {
|
||||
calculator = new JcaPGPDigestCalculatorProviderBuilder()
|
||||
.setProvider(BouncyCastleProvider.PROVIDER_NAME)
|
||||
.build()
|
||||
.get(HashAlgorithmTags.SHA1);
|
||||
} catch (PGPException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
|
||||
PBESecretKeyEncryptor encryptor = new JcePBESecretKeyEncryptorBuilder(
|
||||
PGPSymmetricEncryptionAlgorithms.AES_256.getAlgorithmId())
|
||||
.setProvider(BouncyCastleProvider.PROVIDER_NAME)
|
||||
.build(password.toCharArray());
|
||||
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
|
||||
try {
|
||||
for (OpenPgpV4Fingerprint fingerprint : fingerprints) {
|
||||
// Our unencrypted secret key
|
||||
PGPSecretKey secretKey = keyringConfig.getSecretKeyRings()
|
||||
.getSecretKey(Util.keyIdFromFingerprint(fingerprint));
|
||||
|
||||
if (secretKey == null) {
|
||||
buffer.close();
|
||||
throw new MissingOpenPgpKeyPairException(user);
|
||||
}
|
||||
|
||||
PGPSecretKey encrypted = new PGPSecretKey(
|
||||
secretKey.extractPrivateKey(null),
|
||||
secretKey.getPublicKey(),
|
||||
calculator,
|
||||
true,
|
||||
encryptor);
|
||||
|
||||
buffer.write(encrypted.getEncoded());
|
||||
}
|
||||
|
||||
return new SecretkeyElement(Base64.encode(buffer.toByteArray()));
|
||||
|
||||
} catch (PGPException | IOException e) {
|
||||
throw new SmackOpenPgpException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void restoreSecretKeyBackup(SecretkeyElement secretkeyElement, String password, SecretKeyRestoreSelectionCallback callback)
|
||||
throws SmackOpenPgpException, InvalidBackupCodeException { // TODO: Figure out InvalidBackupCodeException
|
||||
byte[] base64Decoded = Base64.decode(secretkeyElement.getB64Data());
|
||||
|
||||
try {
|
||||
PGPDigestCalculatorProvider calculatorProvider = new JcaPGPDigestCalculatorProviderBuilder()
|
||||
.setProvider(BouncyCastleProvider.PROVIDER_NAME)
|
||||
.build();
|
||||
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(base64Decoded);
|
||||
KeyringConfig keyring = KeyringConfigs.withKeyRingsFromStreams(null, inputStream,
|
||||
KeyringConfigCallbacks.withPassword(password));
|
||||
|
||||
Map<OpenPgpV4Fingerprint, PGPSecretKey> availableKeys = new HashMap<>();
|
||||
OpenPgpV4Fingerprint selectedKey;
|
||||
|
||||
for (PGPSecretKeyRing r : keyring.getSecretKeyRings()) {
|
||||
PGPSecretKey s = r.getSecretKey();
|
||||
PGPPrivateKey privateKey = s.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder(calculatorProvider).build(password.toCharArray()));
|
||||
PGPPublicKey publicKey = s.getPublicKey();
|
||||
PGPSecretKey secretKey = new PGPSecretKey(
|
||||
privateKey,
|
||||
publicKey,
|
||||
calculatorProvider.get(PGPHashAlgorithms.SHA1.getAlgorithmId()),
|
||||
true,
|
||||
null);
|
||||
availableKeys.put(BCOpenPgpProvider.getFingerprint(publicKey), secretKey);
|
||||
}
|
||||
|
||||
selectedKey = callback.selectSecretKeyToRestore(availableKeys.keySet());
|
||||
if (selectedKey != null) {
|
||||
try {
|
||||
keyringConfig.addSecretKey(availableKeys.get(selectedKey).getEncoded());
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOGGER.log(Level.INFO, "Users secret key " + selectedKey.toString() + " is already in keyring. Skip.");
|
||||
}
|
||||
|
||||
try {
|
||||
keyringConfig.addPublicKey(availableKeys.get(selectedKey).getPublicKey().getEncoded());
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOGGER.log(Level.INFO, "Users public key " + selectedKey.toString() + " is already in keyring. Skip.");
|
||||
}
|
||||
primaryKeyFingerprint = selectedKey;
|
||||
writePrivateKeysToFile(keyringConfig, secretKeyringPath());
|
||||
writePublicKeysToFile(keyring, publicKeyringPath());
|
||||
}
|
||||
} catch (PGPException | IOException e) {
|
||||
throw new SmackOpenPgpException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public OpenPgpV4Fingerprint createOpenPgpKeyPair()
|
||||
throws NoSuchAlgorithmException, NoSuchProviderException, SmackOpenPgpException {
|
||||
try {
|
||||
PGPSecretKeyRing ourKey = BCOpenPgpProvider.generateKey(user).generateSecretKeyRing();
|
||||
keyringConfig.addSecretKey(ourKey.getSecretKey().getEncoded());
|
||||
keyringConfig.addPublicKey(ourKey.getPublicKey().getEncoded());
|
||||
writePrivateKeysToFile(keyringConfig, secretKeyringPath());
|
||||
writePublicKeysToFile(keyringConfig, publicKeyringPath());
|
||||
primaryKeyFingerprint = BCOpenPgpProvider.getFingerprint(ourKey.getPublicKey());
|
||||
return primaryKeyFingerprint;
|
||||
} catch (PGPException | IOException e) {
|
||||
throw new SmackOpenPgpException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getPubkeysLatestUpdateDate(BareJid owner, OpenPgpV4Fingerprint fingerprint) {
|
||||
return readDateFromFile(publicKeyUpdateDatePath(owner, fingerprint));
|
||||
}
|
||||
|
||||
private File secretKeyringPath() {
|
||||
return new File(contactsPath(user), "secring.skr");
|
||||
}
|
||||
|
||||
private File publicKeyringPath() {
|
||||
return new File(contactsPath(user), "pubring.pkr");
|
||||
}
|
||||
|
||||
private File contactsPath() {
|
||||
return new File(basePath, user.toString() + "/users");
|
||||
}
|
||||
|
||||
private File contactsPath(BareJid contact) {
|
||||
return new File(contactsPath(), contact.toString());
|
||||
}
|
||||
|
||||
private File contactsList(BareJid contact) {
|
||||
return new File(contactsPath(contact), "metadata.list");
|
||||
}
|
||||
|
||||
private File publicKeyUpdateDatePath(BareJid owner, OpenPgpV4Fingerprint fingerprint) {
|
||||
return new File(contactsPath(owner), fingerprint.toString() + "-update.date");
|
||||
}
|
||||
|
||||
private static void writeDateToFile(File file, Date date) throws IOException {
|
||||
if (!file.exists()) {
|
||||
file.getParentFile().mkdirs();
|
||||
file.createNewFile();
|
||||
}
|
||||
|
||||
BufferedWriter writer = null;
|
||||
try {
|
||||
writer = new BufferedWriter(new OutputStreamWriter(
|
||||
new FileOutputStream(file), "UTF8"));
|
||||
writer.write(XmppDateTime.formatXEP0082Date(date));
|
||||
writer.flush();
|
||||
writer.close();
|
||||
} catch (IOException e) {
|
||||
if (writer != null) {
|
||||
writer.close();
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private static Date readDateFromFile(File file) {
|
||||
if (!file.exists()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
BufferedReader reader = null;
|
||||
Date result = null;
|
||||
try {
|
||||
reader = new BufferedReader(new InputStreamReader(
|
||||
new FileInputStream(file), "UTF8"));
|
||||
String line = reader.readLine();
|
||||
if (!line.isEmpty()) {
|
||||
result = XmppDateTime.parseXEP0082Date(line);
|
||||
}
|
||||
reader.close();
|
||||
reader = null;
|
||||
} catch (UnsupportedEncodingException | FileNotFoundException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (IOException e) {
|
||||
LOGGER.log(Level.WARNING, "Exception while reading date.", e);
|
||||
} catch (ParseException e) {
|
||||
LOGGER.log(Level.WARNING, "Could not parse date", e);
|
||||
result = null;
|
||||
}
|
||||
|
||||
if (reader != null) {
|
||||
try {
|
||||
reader.close();
|
||||
} catch (IOException e) {
|
||||
LOGGER.log(Level.WARNING, "Could not close reader.", e);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void addPublicKeysFromFile(InMemoryKeyring keyring,
|
||||
File pubring,
|
||||
KeyringConfigCallback passwordCallback)
|
||||
throws IOException, PGPException {
|
||||
if (!pubring.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
InputStream inputStream = new FileInputStream(pubring);
|
||||
KeyringConfig source = KeyringConfigs.withKeyRingsFromStreams(inputStream, null, passwordCallback);
|
||||
for (PGPPublicKeyRing pubRing : source.getPublicKeyRings()) {
|
||||
for (PGPPublicKey pubKey : pubRing) {
|
||||
try {
|
||||
keyring.addPublicKey(pubKey.getEncoded());
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOGGER.log(Level.INFO, "public key " + Long.toHexString(pubKey.getKeyID()) +
|
||||
" already exists in keyring. Skip.");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static PGPPublicKey addSecretKeysFromFile(InMemoryKeyring keyring,
|
||||
File secring,
|
||||
KeyringConfigCallback passwordCallback)
|
||||
throws IOException, PGPException {
|
||||
if (!secring.exists()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
InputStream inputStream = new FileInputStream(secring);
|
||||
KeyringConfig source = KeyringConfigs.withKeyRingsFromStreams(null, inputStream, passwordCallback);
|
||||
PGPPublicKey lastAdded = null;
|
||||
|
||||
for (PGPSecretKeyRing secRing : source.getSecretKeyRings()) {
|
||||
for (PGPSecretKey secKey : secRing) {
|
||||
keyring.addSecretKey(secKey.getEncoded());
|
||||
// Remember last added secret keys public key -> this will be the primary key
|
||||
if (secKey.getPublicKey() != null) {
|
||||
lastAdded = secKey.getPublicKey();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return lastAdded;
|
||||
}
|
||||
|
||||
private static void writePublicKeysToFile(KeyringConfig keyring, File pubring)
|
||||
throws IOException, PGPException {
|
||||
writeBytesToFile(keyring.getPublicKeyRings().getEncoded(), pubring);
|
||||
}
|
||||
|
||||
private static void writePrivateKeysToFile(KeyringConfig keyring, File secring)
|
||||
throws IOException, PGPException {
|
||||
writeBytesToFile(keyring.getSecretKeyRings().getEncoded(), secring);
|
||||
}
|
||||
|
||||
private static void writeBytesToFile(byte[] bytes, File file) throws IOException {
|
||||
if (!file.exists()) {
|
||||
file.getParentFile().mkdirs();
|
||||
file.createNewFile();
|
||||
}
|
||||
|
||||
FileOutputStream outputStream = null;
|
||||
try {
|
||||
outputStream = new FileOutputStream(file);
|
||||
BufferedOutputStream bufferedStream = new BufferedOutputStream(outputStream);
|
||||
|
||||
bufferedStream.write(bytes);
|
||||
bufferedStream.flush();
|
||||
bufferedStream.close();
|
||||
} catch (IOException e) {
|
||||
if (outputStream != null) {
|
||||
outputStream.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyringConfig getKeyringConfig() {
|
||||
return keyringConfig;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
package org.jivesoftware.smackx.ox.bouncycastle;
|
||||
|
||||
public interface FileBasedOpenPgpStore {
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
package org.jivesoftware.smackx.ox.bouncycastle;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jivesoftware.smack.util.MultiMap;
|
||||
import org.jivesoftware.smackx.ox.OpenPgpStore;
|
||||
import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint;
|
||||
import org.jivesoftware.smackx.ox.exception.MissingOpenPgpKeyPairException;
|
||||
import org.jivesoftware.smackx.ox.exception.MissingOpenPgpPublicKeyException;
|
||||
import org.jivesoftware.smackx.ox.exception.SmackOpenPgpException;
|
||||
|
||||
import org.jxmpp.jid.BareJid;
|
||||
|
||||
public class FileBasedPainlessOpenPgpStore implements OpenPgpStore, FileBasedOpenPgpStore {
|
||||
|
||||
@Override
|
||||
public OpenPgpV4Fingerprint getPrimaryOpenPgpKeyPairFingerprint() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPrimaryOpenPgpKeyPairFingerprint(OpenPgpV4Fingerprint fingerprint) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<OpenPgpV4Fingerprint> getAvailableKeyPairFingerprints() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<OpenPgpV4Fingerprint, Date> getAvailableKeysFingerprints(BareJid contact) throws SmackOpenPgpException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<OpenPgpV4Fingerprint, Date> getAnnouncedKeysFingerprints(BareJid contact) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAnnouncedKeysFingerprints(BareJid contact, Map<OpenPgpV4Fingerprint, Date> fingerprints) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getPubkeysLastRevision(BareJid owner, OpenPgpV4Fingerprint fingerprint) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPubkeysLastRevision(BareJid owner, OpenPgpV4Fingerprint fingerprint, Date revision) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public MultiMap<BareJid, OpenPgpV4Fingerprint> getAllContactsTrustedFingerprints() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getPublicKeyBytes(BareJid owner, OpenPgpV4Fingerprint fingerprint)
|
||||
throws MissingOpenPgpPublicKeyException {
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getSecretKeyBytes(BareJid owner, OpenPgpV4Fingerprint fingerprint)
|
||||
throws MissingOpenPgpKeyPairException {
|
||||
return new byte[0];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,256 @@
|
|||
package org.jivesoftware.smackx.ox.bouncycastle;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jivesoftware.smack.util.MultiMap;
|
||||
import org.jivesoftware.smackx.ox.OpenPgpProvider;
|
||||
import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint;
|
||||
import org.jivesoftware.smackx.ox.callback.SmackMissingOpenPgpPublicKeyCallback;
|
||||
import org.jivesoftware.smackx.ox.element.CryptElement;
|
||||
import org.jivesoftware.smackx.ox.element.SignElement;
|
||||
import org.jivesoftware.smackx.ox.element.SigncryptElement;
|
||||
import org.jivesoftware.smackx.ox.exception.MissingOpenPgpKeyPairException;
|
||||
import org.jivesoftware.smackx.ox.exception.MissingOpenPgpPublicKeyException;
|
||||
import org.jivesoftware.smackx.ox.exception.MissingUserIdOnKeyException;
|
||||
import org.jivesoftware.smackx.ox.exception.SmackOpenPgpException;
|
||||
import org.jivesoftware.smackx.ox.util.DecryptedBytesAndMetadata;
|
||||
import org.jivesoftware.smackx.ox.util.KeyBytesAndFingerprint;
|
||||
|
||||
import de.vanitasvitae.crypto.pgpainless.PGPainless;
|
||||
import de.vanitasvitae.crypto.pgpainless.decryption_verification.DecryptionStream;
|
||||
import de.vanitasvitae.crypto.pgpainless.decryption_verification.MissingPublicKeyCallback;
|
||||
import de.vanitasvitae.crypto.pgpainless.decryption_verification.PainlessResult;
|
||||
import de.vanitasvitae.crypto.pgpainless.key.generation.type.length.RsaLength;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
import org.bouncycastle.util.io.Streams;
|
||||
import org.jxmpp.jid.BareJid;
|
||||
|
||||
public class PainlessOpenPgpProvider implements OpenPgpProvider {
|
||||
|
||||
private final PainlessOpenPgpStore store;
|
||||
private final BareJid owner;
|
||||
|
||||
public PainlessOpenPgpProvider(BareJid owner, PainlessOpenPgpStore store) {
|
||||
this.owner = owner;
|
||||
this.store = store;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] signAndEncrypt(SigncryptElement element,
|
||||
OpenPgpV4Fingerprint signingKey,
|
||||
MultiMap<BareJid, OpenPgpV4Fingerprint> encryptionKeys)
|
||||
throws MissingOpenPgpKeyPairException, MissingOpenPgpPublicKeyException, SmackOpenPgpException,
|
||||
IOException {
|
||||
|
||||
Set<PGPPublicKeyRing> allRecipientsKeys = getEncryptionKeys(encryptionKeys);
|
||||
PGPSecretKeyRing signingKeyRing = getSigningKey(signingKey);
|
||||
|
||||
InputStream fromPlain = element.toInputStream();
|
||||
ByteArrayOutputStream encrypted = new ByteArrayOutputStream();
|
||||
OutputStream encryptor;
|
||||
try {
|
||||
encryptor = PGPainless.createEncryptor()
|
||||
.onOutputStream(encrypted)
|
||||
.toRecipients((PGPPublicKeyRing[]) allRecipientsKeys.toArray())
|
||||
.usingSecureAlgorithms()
|
||||
.signWith(store.getSecretKeyProtector(), signingKeyRing)
|
||||
.noArmor();
|
||||
} catch (PGPException e) {
|
||||
throw new SmackOpenPgpException(e);
|
||||
}
|
||||
|
||||
Streams.pipeAll(fromPlain, encryptor);
|
||||
fromPlain.close();
|
||||
encryptor.close();
|
||||
|
||||
return encrypted.toByteArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] sign(SignElement element, OpenPgpV4Fingerprint signingKeyFingerprint)
|
||||
throws MissingOpenPgpKeyPairException, IOException, SmackOpenPgpException {
|
||||
InputStream fromPlain = element.toInputStream();
|
||||
PGPSecretKeyRing signingKeyRing;
|
||||
try {
|
||||
signingKeyRing = store.getSecretKeyRing(owner).getSecretKeyRing(signingKeyFingerprint.getKeyId());
|
||||
} catch (PGPException e) {
|
||||
throw new MissingOpenPgpKeyPairException(owner, e);
|
||||
}
|
||||
|
||||
ByteArrayOutputStream toSigned = new ByteArrayOutputStream();
|
||||
OutputStream signer;
|
||||
try {
|
||||
signer = PGPainless.createEncryptor().onOutputStream(toSigned)
|
||||
.doNotEncrypt()
|
||||
.signWith(store.getSecretKeyProtector(), signingKeyRing)
|
||||
.noArmor();
|
||||
} catch (PGPException e) {
|
||||
throw new SmackOpenPgpException(e);
|
||||
}
|
||||
|
||||
Streams.pipeAll(fromPlain, signer);
|
||||
|
||||
fromPlain.close();
|
||||
signer.close();
|
||||
|
||||
return toSigned.toByteArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] encrypt(CryptElement element, MultiMap<BareJid, OpenPgpV4Fingerprint> encryptionKeyFingerprints)
|
||||
throws MissingOpenPgpPublicKeyException, IOException, SmackOpenPgpException {
|
||||
Set<PGPPublicKeyRing> allRecipientsKeys = getEncryptionKeys(encryptionKeyFingerprints);
|
||||
|
||||
InputStream fromPlain = element.toInputStream();
|
||||
ByteArrayOutputStream encrypted = new ByteArrayOutputStream();
|
||||
OutputStream encryptor;
|
||||
try {
|
||||
encryptor = PGPainless.createEncryptor()
|
||||
.onOutputStream(encrypted)
|
||||
.toRecipients((PGPPublicKeyRing[]) allRecipientsKeys.toArray())
|
||||
.usingSecureAlgorithms()
|
||||
.doNotSign()
|
||||
.noArmor();
|
||||
} catch (PGPException e) {
|
||||
throw new SmackOpenPgpException(e);
|
||||
}
|
||||
|
||||
Streams.pipeAll(fromPlain, encryptor);
|
||||
fromPlain.close();
|
||||
encryptor.close();
|
||||
|
||||
return encrypted.toByteArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DecryptedBytesAndMetadata decrypt(byte[] bytes, BareJid sender, final SmackMissingOpenPgpPublicKeyCallback missingPublicKeyCallback)
|
||||
throws MissingOpenPgpKeyPairException, SmackOpenPgpException, IOException {
|
||||
Set<Long> trustedKeyIds = new HashSet<>();
|
||||
Set<PGPPublicKeyRing> senderKeys = new HashSet<>();
|
||||
InputStream fromEncrypted = new ByteArrayInputStream(bytes);
|
||||
ByteArrayOutputStream toPlain = new ByteArrayOutputStream();
|
||||
DecryptionStream decryptionStream;
|
||||
try {
|
||||
decryptionStream = PGPainless.createDecryptor().onInputStream(fromEncrypted)
|
||||
.decryptWith(store.getSecretKeyRing(owner), store.getSecretKeyProtector())
|
||||
.verifyWith(trustedKeyIds, senderKeys)
|
||||
.handleMissingPublicKeysWith(new MissingPublicKeyCallback() {
|
||||
@Override
|
||||
public void onMissingPublicKeyEncountered(Long aLong) {
|
||||
|
||||
}
|
||||
})
|
||||
.build();
|
||||
} catch (PGPException e) {
|
||||
throw new SmackOpenPgpException(e);
|
||||
}
|
||||
Streams.pipeAll(decryptionStream, toPlain);
|
||||
fromEncrypted.close();
|
||||
decryptionStream.close();
|
||||
|
||||
PainlessResult result = decryptionStream.getResult();
|
||||
|
||||
return new DecryptedBytesAndMetadata(toPlain.toByteArray(),
|
||||
result.getVerifiedSignatureKeyIds(),
|
||||
result.getDecryptionKeyId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] symmetricallyEncryptWithPassword(byte[] bytes, String password)
|
||||
throws SmackOpenPgpException, IOException {
|
||||
try {
|
||||
return PGPainless.encryptWithPassword(bytes, password.toCharArray());
|
||||
} catch (PGPException e) {
|
||||
throw new SmackOpenPgpException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] symmetricallyDecryptWithPassword(byte[] bytes, String password)
|
||||
throws SmackOpenPgpException, IOException {
|
||||
try {
|
||||
return PGPainless.decryptWithPassword(bytes, password.toCharArray());
|
||||
} catch (PGPException e) {
|
||||
throw new SmackOpenPgpException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PainlessOpenPgpStore getStore() {
|
||||
return store;
|
||||
}
|
||||
|
||||
private Set<PGPPublicKeyRing> getEncryptionKeys(MultiMap<BareJid, OpenPgpV4Fingerprint> encryptionKeys)
|
||||
throws IOException, SmackOpenPgpException {
|
||||
Set<PGPPublicKeyRing> allRecipientsKeys = new HashSet<>();
|
||||
|
||||
for (BareJid recipient : encryptionKeys.keySet()) {
|
||||
PGPPublicKeyRingCollection recipientsKeyRings;
|
||||
try {
|
||||
recipientsKeyRings = store.getPublicKeyRings(recipient);
|
||||
for (OpenPgpV4Fingerprint fingerprint : encryptionKeys.getAll(recipient)) {
|
||||
PGPPublicKeyRing ring = recipientsKeyRings.getPublicKeyRing(fingerprint.getKeyId());
|
||||
if (ring != null) allRecipientsKeys.add(ring);
|
||||
}
|
||||
} catch (PGPException e) {
|
||||
throw new SmackOpenPgpException(e);
|
||||
}
|
||||
}
|
||||
|
||||
return allRecipientsKeys;
|
||||
}
|
||||
|
||||
private PGPSecretKeyRing getSigningKey(OpenPgpV4Fingerprint signingKey)
|
||||
throws IOException, MissingOpenPgpKeyPairException {
|
||||
PGPSecretKeyRing signingKeyRing;
|
||||
try {
|
||||
signingKeyRing = store.getSecretKeyRing(owner).getSecretKeyRing(signingKey.getKeyId());
|
||||
} catch (PGPException e) {
|
||||
throw new MissingOpenPgpKeyPairException(owner, e);
|
||||
}
|
||||
return signingKeyRing;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyBytesAndFingerprint generateOpenPgpKeyPair(BareJid owner)
|
||||
throws SmackOpenPgpException, InvalidAlgorithmParameterException, NoSuchAlgorithmException,
|
||||
NoSuchProviderException, IOException {
|
||||
PGPSecretKeyRing secretKey;
|
||||
try {
|
||||
secretKey = PGPainless.generateKeyRing().simpleRsaKeyRing("xmpp:" + owner.toString(), RsaLength._4096);
|
||||
} catch (PGPException e) {
|
||||
throw new SmackOpenPgpException("Could not generate OpenPGP Key Pair.", e);
|
||||
}
|
||||
|
||||
return new KeyBytesAndFingerprint(secretKey.getEncoded(), getFingerprint(secretKey.getPublicKey()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public OpenPgpV4Fingerprint importPublicKey(BareJid owner, byte[] bytes) throws MissingUserIdOnKeyException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OpenPgpV4Fingerprint importSecretKey(BareJid owner, byte[] bytes) throws MissingUserIdOnKeyException {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static OpenPgpV4Fingerprint getFingerprint(PGPPublicKey publicKey) {
|
||||
byte[] hex = Hex.encode(publicKey.getFingerprint());
|
||||
return new OpenPgpV4Fingerprint(hex);
|
||||
}
|
||||
}
|
|
@ -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 java.io.IOException;
|
||||
|
||||
import org.jivesoftware.smackx.ox.OpenPgpStore;
|
||||
|
||||
import de.vanitasvitae.crypto.pgpainless.key.SecretKeyRingProtector;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
||||
import org.jxmpp.jid.BareJid;
|
||||
|
||||
public interface PainlessOpenPgpStore extends OpenPgpStore {
|
||||
|
||||
PGPPublicKeyRingCollection getPublicKeyRings(BareJid owner) throws IOException, PGPException;
|
||||
|
||||
PGPSecretKeyRingCollection getSecretKeyRing(BareJid owner) throws IOException, PGPException;
|
||||
|
||||
void storePublicKeyRing(BareJid owner, PGPPublicKeyRingCollection publicKeys) throws IOException;
|
||||
|
||||
void storeSecretKeyRing(BareJid owner, PGPSecretKeyRingCollection secretKeys) throws IOException;
|
||||
|
||||
SecretKeyRingProtector getSecretKeyProtector();
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package org.jivesoftware.smackx.ox.bouncycastle.selection_strategy;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import de.vanitasvitae.crypto.pgpainless.key.selection.keyring.PublicKeyRingSelectionStrategy;
|
||||
import de.vanitasvitae.crypto.pgpainless.key.selection.keyring.SecretKeyRingSelectionStrategy;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.jxmpp.jid.BareJid;
|
||||
|
||||
public class BareJidUserId {
|
||||
|
||||
public static class PubRingSelectionStrategy extends PublicKeyRingSelectionStrategy<BareJid> {
|
||||
|
||||
@Override
|
||||
public boolean accept(BareJid jid, PGPPublicKeyRing ring) {
|
||||
Iterator<String> userIds = ring.getPublicKey().getUserIDs();
|
||||
while (userIds.hasNext()) {
|
||||
String userId = userIds.next();
|
||||
if (userId.equals("xmpp:" + jid.toString())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static class SecRingSelectionStrategy extends SecretKeyRingSelectionStrategy<BareJid> {
|
||||
|
||||
@Override
|
||||
public boolean accept(BareJid jid, PGPSecretKeyRing ring) {
|
||||
Iterator<String> userIds = ring.getPublicKey().getUserIDs();
|
||||
while (userIds.hasNext()) {
|
||||
String userId = userIds.next();
|
||||
if (userId.equals("xmpp:" + jid.toString())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2018 Paul Schaub.
|
||||
* Copyright 2017 Florian Schmaus.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -14,13 +14,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jivesoftware.smackx.ox.bouncycastle;
|
||||
|
||||
import org.jivesoftware.smackx.ox.OpenPgpStore;
|
||||
|
||||
import name.neuhalfen.projects.crypto.bouncycastle.openpgp.keys.keyrings.KeyringConfig;
|
||||
|
||||
public interface BCOpenPgpStore extends OpenPgpStore {
|
||||
|
||||
KeyringConfig getKeyringConfig();
|
||||
}
|
||||
/**
|
||||
* Providers for XEP-0373: OpenPGP for XMPP using Bouncycastle.
|
||||
*/
|
||||
package org.jivesoftware.smackx.ox.bouncycastle.selection_strategy;
|
|
@ -25,7 +25,7 @@ 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.chat.OpenPgpMessage;
|
||||
import org.jivesoftware.smackx.ox.element.OpenPgpContentElement;
|
||||
import org.jivesoftware.smackx.ox.element.OpenPgpElement;
|
||||
import org.jivesoftware.smackx.ox.element.PubkeyElement;
|
||||
|
@ -54,10 +54,10 @@ public class BouncyCastleOpenPgpProviderTest extends SmackTestSuite {
|
|||
|
||||
// dry exchange keys
|
||||
|
||||
PubkeyElement aliceKeys = aliceProvider.createPubkeyElement(aliceProvider.primaryOpenPgpKeyPairFingerprint());
|
||||
PubkeyElement cheshireKeys = cheshireProvider.createPubkeyElement(cheshireProvider.primaryOpenPgpKeyPairFingerprint());
|
||||
aliceProvider.storePublicKey(cheshire, cheshireProvider.primaryOpenPgpKeyPairFingerprint(), cheshireKeys);
|
||||
cheshireProvider.storePublicKey(alice, aliceProvider.primaryOpenPgpKeyPairFingerprint(), aliceKeys);
|
||||
PubkeyElement aliceKeys = aliceProvider.createPubkeyElement(aliceProvider.getPrimaryOpenPgpKeyPairFingerprint());
|
||||
PubkeyElement cheshireKeys = cheshireProvider.createPubkeyElement(cheshireProvider.getPrimaryOpenPgpKeyPairFingerprint());
|
||||
aliceProvider.storePublicKey(cheshire, cheshireProvider.getPrimaryOpenPgpKeyPairFingerprint(), cheshireKeys);
|
||||
cheshireProvider.storePublicKey(alice, aliceProvider.getPrimaryOpenPgpKeyPairFingerprint(), aliceKeys);
|
||||
|
||||
// Create signed and encrypted message from alice to the cheshire cat
|
||||
SigncryptElement signcryptElement = new SigncryptElement(
|
||||
|
@ -66,11 +66,11 @@ public class BouncyCastleOpenPgpProviderTest extends SmackTestSuite {
|
|||
new Message.Body("en", "How do you know I’m mad?")));
|
||||
OpenPgpElement encrypted = aliceProvider.signAndEncrypt(
|
||||
signcryptElement,
|
||||
aliceProvider.primaryOpenPgpKeyPairFingerprint(),
|
||||
Collections.singleton(cheshireProvider.primaryOpenPgpKeyPairFingerprint()));
|
||||
aliceProvider.getPrimaryOpenPgpKeyPairFingerprint(),
|
||||
Collections.singleton(cheshireProvider.getPrimaryOpenPgpKeyPairFingerprint()));
|
||||
|
||||
// Decrypt the message as the cheshire cat
|
||||
OpenPgpMessage decrypted = cheshireProvider.decryptAndVerify(encrypted, Collections.singleton(aliceProvider.primaryOpenPgpKeyPairFingerprint()));
|
||||
OpenPgpMessage decrypted = cheshireProvider.decryptAndVerify(encrypted, Collections.singleton(aliceProvider.getPrimaryOpenPgpKeyPairFingerprint()));
|
||||
OpenPgpContentElement content = decrypted.getOpenPgpContentElement();
|
||||
|
||||
assertTrue(content instanceof SigncryptElement);
|
||||
|
|
|
@ -24,7 +24,7 @@ import java.io.IOException;
|
|||
import org.jivesoftware.smack.test.util.SmackTestSuite;
|
||||
import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint;
|
||||
import org.jivesoftware.smackx.ox.TestKeys;
|
||||
import org.jivesoftware.smackx.ox.Util;
|
||||
import org.jivesoftware.smackx.ox.util.Util;
|
||||
|
||||
import name.neuhalfen.projects.crypto.bouncycastle.openpgp.keys.callbacks.KeyringConfigCallbacks;
|
||||
import name.neuhalfen.projects.crypto.bouncycastle.openpgp.keys.keyrings.InMemoryKeyring;
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package org.jivesoftware.smackx.ox;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
@ -32,14 +33,18 @@ import org.jivesoftware.smack.chat2.Chat;
|
|||
import org.jivesoftware.smack.chat2.ChatManager;
|
||||
import org.jivesoftware.smack.chat2.IncomingChatMessageListener;
|
||||
import org.jivesoftware.smack.packet.Message;
|
||||
import org.jivesoftware.smack.util.stringencoder.Base64;
|
||||
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
|
||||
import org.jivesoftware.smackx.ox.chat.OpenPgpEncryptedChat;
|
||||
import org.jivesoftware.smackx.ox.chat.OpenPgpFingerprints;
|
||||
import org.jivesoftware.smackx.ox.chat.OpenPgpMessage;
|
||||
import org.jivesoftware.smackx.ox.element.OpenPgpContentElement;
|
||||
import org.jivesoftware.smackx.ox.element.OpenPgpElement;
|
||||
import org.jivesoftware.smackx.ox.element.SigncryptElement;
|
||||
import org.jivesoftware.smackx.ox.exception.MissingOpenPgpKeyPairException;
|
||||
import org.jivesoftware.smackx.ox.exception.MissingOpenPgpPublicKeyException;
|
||||
import org.jivesoftware.smackx.ox.exception.SmackOpenPgpException;
|
||||
import org.jivesoftware.smackx.ox.listener.OpenPgpEncryptedMessageListener;
|
||||
import org.jivesoftware.smackx.ox.util.DecryptedBytesAndMetadata;
|
||||
|
||||
import org.jxmpp.jid.BareJid;
|
||||
import org.jxmpp.jid.EntityBareJid;
|
||||
|
@ -62,6 +67,7 @@ public final class OXInstantMessagingManager extends Manager {
|
|||
private final ChatManager chatManager;
|
||||
|
||||
private final Set<OpenPgpEncryptedMessageListener> chatMessageListeners = new HashSet<>();
|
||||
private final Map<BareJid, OpenPgpEncryptedChat> chats = new HashMap<>();
|
||||
|
||||
private OXInstantMessagingManager(final XMPPConnection connection) {
|
||||
super(connection);
|
||||
|
@ -144,23 +150,29 @@ public final class OXInstantMessagingManager extends Manager {
|
|||
|
||||
try {
|
||||
OpenPgpEncryptedChat encryptedChat = chatWith(from);
|
||||
OpenPgpMessage decrypted = provider.decryptAndVerify(element, provider.availableOpenPgpPublicKeysFingerprints(from.asBareJid()));
|
||||
OpenPgpContentElement contentElement = decrypted.getOpenPgpContentElement();
|
||||
if (decrypted.getState() != OpenPgpMessage.State.signcrypt) {
|
||||
DecryptedBytesAndMetadata decryptedBytes = provider.decrypt(Base64.decode(
|
||||
element.getEncryptedBase64MessageContent()),
|
||||
from.asBareJid(),
|
||||
null);
|
||||
|
||||
OpenPgpMessage openPgpMessage = new OpenPgpMessage(decryptedBytes.getBytes(),
|
||||
new OpenPgpMessage.Metadata(decryptedBytes.getDecryptionKey(),
|
||||
decryptedBytes.getVerifiedSignatures()));
|
||||
|
||||
OpenPgpContentElement contentElement = openPgpMessage.getOpenPgpContentElement();
|
||||
if (openPgpMessage.getState() != OpenPgpMessage.State.signcrypt) {
|
||||
LOGGER.log(Level.WARNING, "Decrypted content is not a signcrypt element. Ignore it.");
|
||||
return;
|
||||
}
|
||||
|
||||
SigncryptElement signcryptElement = (SigncryptElement) contentElement;
|
||||
for (OpenPgpEncryptedMessageListener l : chatMessageListeners) {
|
||||
l.newIncomingEncryptedMessage(from, message, signcryptElement, encryptedChat);
|
||||
l.newIncomingOxMessage(from, message, signcryptElement, encryptedChat);
|
||||
}
|
||||
} catch (SmackOpenPgpException e) {
|
||||
LOGGER.log(Level.WARNING, "Could not start chat with " + from, e);
|
||||
} catch (InterruptedException | XMPPException.XMPPErrorException | SmackException.NotConnectedException | SmackException.NoResponseException e) {
|
||||
LOGGER.log(Level.WARNING, "Something went wrong.", e);
|
||||
} catch (MissingOpenPgpPublicKeyException e) {
|
||||
LOGGER.log(Level.WARNING, "Could not verify message " + message.getStanzaId() + ": Missing senders public key " + e.getFingerprint().toString(), e);
|
||||
} catch (MissingOpenPgpKeyPairException e) {
|
||||
LOGGER.log(Level.WARNING, "Could not decrypt message " + message.getStanzaId() + ": Missing secret key", e);
|
||||
} catch (XmlPullParserException | IOException e) {
|
||||
|
|
|
@ -16,14 +16,18 @@
|
|||
*/
|
||||
package org.jivesoftware.smackx.ox;
|
||||
|
||||
import static org.jivesoftware.smackx.ox.PubSubDelegate.PEP_NODE_PUBLIC_KEYS;
|
||||
import static org.jivesoftware.smackx.ox.PubSubDelegate.PEP_NODE_PUBLIC_KEYS_NOTIFY;
|
||||
import static org.jivesoftware.smackx.ox.PubSubDelegate.fetchPubkey;
|
||||
import static org.jivesoftware.smackx.ox.PubSubDelegate.publishPublicKey;
|
||||
import static org.jivesoftware.smackx.ox.util.PubSubDelegate.PEP_NODE_PUBLIC_KEYS;
|
||||
import static org.jivesoftware.smackx.ox.util.PubSubDelegate.PEP_NODE_PUBLIC_KEYS_NOTIFY;
|
||||
import static org.jivesoftware.smackx.ox.util.PubSubDelegate.fetchPubkey;
|
||||
import static org.jivesoftware.smackx.ox.util.PubSubDelegate.publishPublicKey;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
@ -37,18 +41,23 @@ import org.jivesoftware.smack.XMPPConnection;
|
|||
import org.jivesoftware.smack.XMPPException;
|
||||
import org.jivesoftware.smack.packet.Message;
|
||||
import org.jivesoftware.smack.util.Async;
|
||||
import org.jivesoftware.smack.util.stringencoder.Base64;
|
||||
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
|
||||
import org.jivesoftware.smackx.ox.callback.AskForBackupCodeCallback;
|
||||
import org.jivesoftware.smackx.ox.callback.DisplayBackupCodeCallback;
|
||||
import org.jivesoftware.smackx.ox.callback.SecretKeyBackupSelectionCallback;
|
||||
import org.jivesoftware.smackx.ox.callback.SecretKeyRestoreSelectionCallback;
|
||||
import org.jivesoftware.smackx.ox.chat.OpenPgpFingerprints;
|
||||
import org.jivesoftware.smackx.ox.element.PubkeyElement;
|
||||
import org.jivesoftware.smackx.ox.element.PublicKeysListElement;
|
||||
import org.jivesoftware.smackx.ox.element.SecretkeyElement;
|
||||
import org.jivesoftware.smackx.ox.exception.InvalidBackupCodeException;
|
||||
import org.jivesoftware.smackx.ox.exception.MissingOpenPgpKeyPairException;
|
||||
import org.jivesoftware.smackx.ox.exception.MissingOpenPgpPublicKeyException;
|
||||
import org.jivesoftware.smackx.ox.exception.MissingUserIdOnKeyException;
|
||||
import org.jivesoftware.smackx.ox.exception.SmackOpenPgpException;
|
||||
import org.jivesoftware.smackx.ox.util.KeyBytesAndFingerprint;
|
||||
import org.jivesoftware.smackx.ox.util.PubSubDelegate;
|
||||
import org.jivesoftware.smackx.pep.PEPListener;
|
||||
import org.jivesoftware.smackx.pep.PEPManager;
|
||||
import org.jivesoftware.smackx.pubsub.EventElement;
|
||||
|
@ -132,20 +141,35 @@ public final class OpenPgpManager extends Manager {
|
|||
* @throws SmackException.NotConnectedException
|
||||
* @throws SmackException.NoResponseException
|
||||
*/
|
||||
public void announceSupportAndPublish() throws NoSuchAlgorithmException, NoSuchProviderException, SmackOpenPgpException,
|
||||
public void announceSupportAndPublish()
|
||||
throws NoSuchAlgorithmException, NoSuchProviderException, SmackOpenPgpException,
|
||||
InterruptedException, PubSubException.NotALeafNodeException, XMPPException.XMPPErrorException,
|
||||
SmackException.NotConnectedException, SmackException.NoResponseException {
|
||||
SmackException.NotConnectedException, SmackException.NoResponseException, IOException,
|
||||
InvalidAlgorithmParameterException, SmackException.NotLoggedInException {
|
||||
throwIfNoProviderSet();
|
||||
throwIfNotAuthenticated();
|
||||
|
||||
BareJid ourJid = connection().getUser().asBareJid();
|
||||
|
||||
OpenPgpV4Fingerprint primaryFingerprint = getOurFingerprint();
|
||||
|
||||
OpenPgpV4Fingerprint primaryFingerprint = provider.primaryOpenPgpKeyPairFingerprint();
|
||||
if (primaryFingerprint == null) {
|
||||
primaryFingerprint = provider.createOpenPgpKeyPair();
|
||||
|
||||
KeyBytesAndFingerprint bytesAndFingerprint = provider.generateOpenPgpKeyPair(ourJid);
|
||||
primaryFingerprint = bytesAndFingerprint.getFingerprint();
|
||||
|
||||
// This should never throw, since we set our jid literally one line above this comment.
|
||||
try {
|
||||
provider.importSecretKey(ourJid, bytesAndFingerprint.getBytes());
|
||||
} catch (MissingUserIdOnKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Create <pubkey/> element
|
||||
PubkeyElement pubkeyElement;
|
||||
try {
|
||||
pubkeyElement = provider.createPubkeyElement(primaryFingerprint);
|
||||
pubkeyElement = createPubkeyElement(ourJid, primaryFingerprint, new Date());
|
||||
} catch (MissingOpenPgpPublicKeyException e) {
|
||||
throw new AssertionError("Cannot publish our public key, since it is missing (MUST NOT happen!)");
|
||||
}
|
||||
|
@ -166,7 +190,7 @@ public final class OpenPgpManager extends Manager {
|
|||
*/
|
||||
public OpenPgpV4Fingerprint getOurFingerprint() {
|
||||
throwIfNoProviderSet();
|
||||
return provider.primaryOpenPgpKeyPairFingerprint();
|
||||
return provider.getStore().getPrimaryOpenPgpKeyPairFingerprint();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -183,7 +207,8 @@ public final class OpenPgpManager extends Manager {
|
|||
*/
|
||||
public boolean serverSupportsSecretKeyBackups()
|
||||
throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
|
||||
SmackException.NoResponseException {
|
||||
SmackException.NoResponseException, SmackException.NotLoggedInException {
|
||||
throwIfNotAuthenticated();
|
||||
boolean pep = PEPManager.getInstanceFor(connection()).isSupported();
|
||||
boolean whitelist = PubSubManager.getInstance(connection(), connection().getUser().asBareJid())
|
||||
.getSupportedFeatures().containsFeature("http://jabber.org/protocol/pubsub#access-whitelist");
|
||||
|
@ -197,7 +222,6 @@ public final class OpenPgpManager extends Manager {
|
|||
*
|
||||
* @param displayCodeCallback callback, which will receive the backup password used to encrypt the secret key.
|
||||
* @param selectKeyCallback callback, which will receive the users choice of which keys will be backed up.
|
||||
* @throws SmackOpenPgpException if the secret key is corrupted or can for some reason not be serialized.
|
||||
* @throws InterruptedException
|
||||
* @throws PubSubException.NotALeafNodeException
|
||||
* @throws XMPPException.XMPPErrorException
|
||||
|
@ -206,14 +230,20 @@ public final class OpenPgpManager extends Manager {
|
|||
*/
|
||||
public void backupSecretKeyToServer(DisplayBackupCodeCallback displayCodeCallback,
|
||||
SecretKeyBackupSelectionCallback selectKeyCallback)
|
||||
throws SmackOpenPgpException, InterruptedException, PubSubException.NotALeafNodeException,
|
||||
throws InterruptedException, PubSubException.NotALeafNodeException,
|
||||
XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException,
|
||||
MissingOpenPgpKeyPairException {
|
||||
SmackException.NotLoggedInException {
|
||||
throwIfNoProviderSet();
|
||||
throwIfNotAuthenticated();
|
||||
|
||||
BareJid ownJid = connection().getUser().asBareJid();
|
||||
|
||||
String backupCode = generateBackupPassword();
|
||||
Set<OpenPgpV4Fingerprint> availableKeyPairs = provider.availableOpenPgpKeyPairFingerprints();
|
||||
SecretkeyElement secretKey = provider.createSecretkeyElement(
|
||||
selectKeyCallback.selectKeysToBackup(availableKeyPairs), backupCode);
|
||||
Set<OpenPgpV4Fingerprint> availableKeyPairs = provider.getStore().getAvailableKeyPairFingerprints();
|
||||
Set<OpenPgpV4Fingerprint> selectedKeyPairs = selectKeyCallback.selectKeysToBackup(availableKeyPairs);
|
||||
|
||||
SecretkeyElement secretKey = createSecretkeyElement(ownJid, selectedKeyPairs, backupCode);
|
||||
|
||||
PubSubDelegate.depositSecretKey(connection(), secretKey);
|
||||
displayCodeCallback.displayBackupCode(backupCode);
|
||||
}
|
||||
|
@ -228,7 +258,8 @@ public final class OpenPgpManager extends Manager {
|
|||
*/
|
||||
public void deleteSecretKeyServerBackup()
|
||||
throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
|
||||
SmackException.NoResponseException {
|
||||
SmackException.NoResponseException, SmackException.NotLoggedInException {
|
||||
throwIfNotAuthenticated();
|
||||
PubSubDelegate.deleteSecretKeyNode(connection());
|
||||
}
|
||||
|
||||
|
@ -249,10 +280,16 @@ public final class OpenPgpManager extends Manager {
|
|||
SecretKeyRestoreSelectionCallback selectionCallback)
|
||||
throws InterruptedException, PubSubException.NotALeafNodeException, XMPPException.XMPPErrorException,
|
||||
SmackException.NotConnectedException, SmackException.NoResponseException, SmackOpenPgpException,
|
||||
InvalidBackupCodeException {
|
||||
InvalidBackupCodeException, SmackException.NotLoggedInException {
|
||||
throwIfNoProviderSet();
|
||||
throwIfNotAuthenticated();
|
||||
SecretkeyElement backup = PubSubDelegate.fetchSecretKey(connection());
|
||||
provider.restoreSecretKeyBackup(backup, codeCallback.askForBackupCode(), selectionCallback);
|
||||
if (backup == null) {
|
||||
// TODO
|
||||
return;
|
||||
}
|
||||
byte[] encrypted = Base64.decode(backup.getB64Data());
|
||||
// provider.restoreSecretKeyBackup(backup, codeCallback.askForBackupCode(), selectionCallback);
|
||||
// TODO: catch InvalidBackupCodeException in order to prevent re-fetching the backup on next try.
|
||||
}
|
||||
|
||||
|
@ -270,22 +307,28 @@ public final class OpenPgpManager extends Manager {
|
|||
public OpenPgpFingerprints determineContactsKeys(BareJid jid)
|
||||
throws SmackOpenPgpException, InterruptedException, XMPPException.XMPPErrorException,
|
||||
SmackException.NotConnectedException, SmackException.NoResponseException {
|
||||
Set<OpenPgpV4Fingerprint> announced = provider.announcedOpenPgpKeyFingerprints(jid);
|
||||
Set<OpenPgpV4Fingerprint> available = provider.availableOpenPgpPublicKeysFingerprints(jid);
|
||||
Set<OpenPgpV4Fingerprint> announced = provider.getStore().getAnnouncedKeysFingerprints(jid).keySet();
|
||||
Set<OpenPgpV4Fingerprint> available = provider.getStore().getAvailableKeysFingerprints(jid).keySet();
|
||||
Map<OpenPgpV4Fingerprint, Throwable> unfetched = new HashMap<>();
|
||||
for (OpenPgpV4Fingerprint f : announced) {
|
||||
if (!available.contains(f)) {
|
||||
try {
|
||||
PubkeyElement pubkeyElement = PubSubDelegate.fetchPubkey(connection(), jid, f);
|
||||
provider.storePublicKey(jid, f, pubkeyElement);
|
||||
if (pubkeyElement == null) {
|
||||
continue;
|
||||
}
|
||||
processPublicKey(pubkeyElement, jid);
|
||||
available.add(f);
|
||||
} catch (PubSubException.NotAPubSubNodeException | PubSubException.NotALeafNodeException e) {
|
||||
LOGGER.log(Level.WARNING, "Could not fetch public key " + f.toString() + " of user " + jid.toString(), e);
|
||||
unfetched.put(f, e);
|
||||
} catch (MissingUserIdOnKeyException e) {
|
||||
LOGGER.log(Level.WARNING, "Key does not contain user-id of " + jid + ". Ignoring the key.", e);
|
||||
unfetched.put(f, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new OpenPgpFingerprints(announced, available, unfetched);
|
||||
return new OpenPgpFingerprints(jid, announced, available, unfetched);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -295,7 +338,7 @@ public final class OpenPgpManager extends Manager {
|
|||
*/
|
||||
private final PEPListener metadataListener = new PEPListener() {
|
||||
@Override
|
||||
public void eventReceived(final EntityBareJid from, final EventElement event, Message message) {
|
||||
public void eventReceived(final EntityBareJid from, final EventElement event, final Message message) {
|
||||
if (PEP_NODE_PUBLIC_KEYS.equals(event.getEvent().getNode())) {
|
||||
final BareJid contact = from.asBareJid();
|
||||
LOGGER.log(Level.INFO, "Received OpenPGP metadata update from " + contact);
|
||||
|
@ -305,17 +348,22 @@ public final class OpenPgpManager extends Manager {
|
|||
ItemsExtension items = (ItemsExtension) event.getExtensions().get(0);
|
||||
PayloadItem<?> payload = (PayloadItem) items.getItems().get(0);
|
||||
PublicKeysListElement listElement = (PublicKeysListElement) payload.getPayload();
|
||||
provider.storePublicKeysList(connection(), listElement, contact);
|
||||
|
||||
Map<OpenPgpV4Fingerprint, Date> announcedKeys = new HashMap<>();
|
||||
for (OpenPgpV4Fingerprint f : listElement.getMetadata().keySet()) {
|
||||
PublicKeysListElement.PubkeyMetadataElement meta = listElement.getMetadata().get(f);
|
||||
announcedKeys.put(meta.getV4Fingerprint(), meta.getDate());
|
||||
}
|
||||
|
||||
provider.getStore().setAnnouncedKeysFingerprints(contact, announcedKeys);
|
||||
|
||||
Set<OpenPgpV4Fingerprint> missingKeys = listElement.getMetadata().keySet();
|
||||
|
||||
try {
|
||||
provider.storePublicKeysList(connection(), listElement, contact);
|
||||
missingKeys.removeAll(provider.availableOpenPgpPublicKeysFingerprints(contact));
|
||||
missingKeys.removeAll(provider.getStore().getAvailableKeysFingerprints(contact).keySet());
|
||||
for (OpenPgpV4Fingerprint missing : missingKeys) {
|
||||
try {
|
||||
PubkeyElement pubkeyElement = fetchPubkey(connection(), contact, missing);
|
||||
provider.storePublicKey(contact, missing, pubkeyElement);
|
||||
processPublicKey(pubkeyElement, contact);
|
||||
} catch (Exception e) {
|
||||
LOGGER.log(Level.WARNING, "Error fetching missing OpenPGP key " + missing.toString(), e);
|
||||
}
|
||||
|
@ -329,6 +377,16 @@ public final class OpenPgpManager extends Manager {
|
|||
}
|
||||
};
|
||||
|
||||
/*
|
||||
Private stuff.
|
||||
*/
|
||||
|
||||
private void processPublicKey(PubkeyElement pubkeyElement, BareJid owner)
|
||||
throws MissingUserIdOnKeyException {
|
||||
byte[] base64 = pubkeyElement.getDataElement().getB64Data();
|
||||
provider.importPublicKey(owner, Base64.decode(base64));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a secure backup code.
|
||||
*
|
||||
|
@ -357,6 +415,39 @@ public final class OpenPgpManager extends Manager {
|
|||
return code.toString();
|
||||
}
|
||||
|
||||
private PubkeyElement createPubkeyElement(BareJid owner,
|
||||
OpenPgpV4Fingerprint fingerprint,
|
||||
Date date)
|
||||
throws MissingOpenPgpPublicKeyException {
|
||||
byte[] keyBytes = provider.getStore().getPublicKeyBytes(owner, fingerprint);
|
||||
return createPubkeyElement(keyBytes, date);
|
||||
}
|
||||
|
||||
private static PubkeyElement createPubkeyElement(byte[] bytes, Date date) {
|
||||
return new PubkeyElement(new PubkeyElement.PubkeyDataElement(Base64.encode(bytes)), date);
|
||||
}
|
||||
|
||||
private SecretkeyElement createSecretkeyElement(BareJid owner,
|
||||
Set<OpenPgpV4Fingerprint> fingerprints,
|
||||
String backupCode) {
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
for (OpenPgpV4Fingerprint fingerprint : fingerprints) {
|
||||
try {
|
||||
byte[] bytes = provider.getStore().getSecretKeyBytes(owner, fingerprint);
|
||||
buffer.write(bytes);
|
||||
} catch (MissingOpenPgpKeyPairException | IOException e) {
|
||||
LOGGER.log(Level.WARNING, "Cannot backup secret key " + Long.toHexString(fingerprint.getKeyId()) + ".", e);
|
||||
}
|
||||
|
||||
}
|
||||
return createSecretkeyElement(buffer.toByteArray(), backupCode);
|
||||
}
|
||||
|
||||
private SecretkeyElement createSecretkeyElement(byte[] keys, String backupCode) {
|
||||
byte[] encrypted = provider.symmetricallyEncryptWithPassword(keys, backupCode);
|
||||
return new SecretkeyElement(Base64.encode(encrypted));
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw an {@link IllegalStateException} if no {@link OpenPgpProvider} is set.
|
||||
* The OpenPgpProvider is used to process information related to RFC-4880.
|
||||
|
@ -366,4 +457,10 @@ public final class OpenPgpManager extends Manager {
|
|||
throw new IllegalStateException("No OpenPgpProvider set!");
|
||||
}
|
||||
}
|
||||
|
||||
private void throwIfNotAuthenticated() throws SmackException.NotLoggedInException {
|
||||
if (!connection().isAuthenticated()) {
|
||||
throw new SmackException.NotLoggedInException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,122 +16,126 @@
|
|||
*/
|
||||
package org.jivesoftware.smackx.ox;
|
||||
|
||||
import java.util.Set;
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
|
||||
import org.jivesoftware.smack.util.MultiMap;
|
||||
import org.jivesoftware.smackx.ox.callback.SmackMissingOpenPgpPublicKeyCallback;
|
||||
import org.jivesoftware.smackx.ox.element.CryptElement;
|
||||
import org.jivesoftware.smackx.ox.element.OpenPgpContentElement;
|
||||
import org.jivesoftware.smackx.ox.element.OpenPgpElement;
|
||||
import org.jivesoftware.smackx.ox.element.SignElement;
|
||||
import org.jivesoftware.smackx.ox.element.SigncryptElement;
|
||||
import org.jivesoftware.smackx.ox.exception.MissingOpenPgpKeyPairException;
|
||||
import org.jivesoftware.smackx.ox.exception.MissingOpenPgpPublicKeyException;
|
||||
import org.jivesoftware.smackx.ox.exception.MissingUserIdOnKeyException;
|
||||
import org.jivesoftware.smackx.ox.exception.SmackOpenPgpException;
|
||||
import org.jivesoftware.smackx.ox.util.DecryptedBytesAndMetadata;
|
||||
import org.jivesoftware.smackx.ox.util.KeyBytesAndFingerprint;
|
||||
|
||||
public interface OpenPgpProvider extends OpenPgpStore {
|
||||
import org.jxmpp.jid.BareJid;
|
||||
|
||||
public interface OpenPgpProvider {
|
||||
|
||||
/**
|
||||
* Sign and encrypt a {@link SigncryptElement} element for usage within the context of instant messaging.
|
||||
* The resulting {@link OpenPgpElement} contains a Base64 encoded, unarmored OpenPGP message,
|
||||
* which can be decrypted by each recipient, as well as by ourselves.
|
||||
* The resulting byte array can be decrypted by each recipient, as well as all devices of the user.
|
||||
* The message contains a signature made by our key.
|
||||
*
|
||||
* @see <a href="https://xmpp.org/extensions/xep-0373.html#signcrypt">XEP-0373 §3</a>
|
||||
* @see <a href="https://xmpp.org/extensions/xep-0374.html#openpgp-secured-im">XEP-0374 §2.1</a>
|
||||
*
|
||||
* @param element {@link SigncryptElement} which contains the content of the message as plaintext.
|
||||
* @param signingKey {@link OpenPgpV4Fingerprint} of the signing key.
|
||||
* @param encryptionKeys {@link Set} containing all {@link OpenPgpV4Fingerprint}s of keys which will
|
||||
* @param encryptionKeys {@link MultiMap} containing all {@link OpenPgpV4Fingerprint}s of recipients which will
|
||||
* be able to decrypt the message.
|
||||
* @return encrypted {@link OpenPgpElement} which contains the encrypted, encoded message.
|
||||
* @return encrypted and signed data which contains the encrypted, encoded message.
|
||||
*
|
||||
* @throws MissingOpenPgpKeyPairException if the OpenPGP key pair with the given {@link OpenPgpV4Fingerprint}
|
||||
* is not available.
|
||||
* @throws MissingOpenPgpKeyPairException if any of the OpenPGP public keys whose {@link OpenPgpV4Fingerprint}
|
||||
* is listed in {@code encryptionKeys} is not available.
|
||||
*/
|
||||
OpenPgpElement signAndEncrypt(SigncryptElement element,
|
||||
OpenPgpV4Fingerprint signingKey,
|
||||
Set<OpenPgpV4Fingerprint> encryptionKeys)
|
||||
throws MissingOpenPgpKeyPairException, MissingOpenPgpPublicKeyException;
|
||||
byte[] signAndEncrypt(SigncryptElement element,
|
||||
OpenPgpV4Fingerprint signingKey,
|
||||
MultiMap<BareJid, OpenPgpV4Fingerprint> encryptionKeys)
|
||||
throws MissingOpenPgpKeyPairException, MissingOpenPgpPublicKeyException, SmackOpenPgpException, IOException;
|
||||
|
||||
/**
|
||||
* Decrypt an incoming {@link OpenPgpElement} which must contain a {@link SigncryptElement} and verify
|
||||
* the signature made by the sender in the context of instant messaging.
|
||||
*
|
||||
* @see <a href="https://xmpp.org/extensions/xep-0374.html#openpgp-secured-im">XEP-0374 §2.1</a>
|
||||
* @param element {@link OpenPgpElement} which contains an encrypted and signed {@link SigncryptElement}.
|
||||
* @param sendersKeys {@link Set} of the senders {@link OpenPgpV4Fingerprint}s.
|
||||
* It is required, that one of those keys was used for signing the message.
|
||||
* @return decrypted {@link OpenPgpMessage} which contains the decrypted {@link SigncryptElement}.
|
||||
* @throws MissingOpenPgpKeyPairException if we have no OpenPGP key pair to decrypt the message.
|
||||
* @throws MissingOpenPgpPublicKeyException if we do not have the public OpenPGP key of the sender to
|
||||
* verify the signature on the message.
|
||||
*/
|
||||
OpenPgpMessage decryptAndVerify(OpenPgpElement element, Set<OpenPgpV4Fingerprint> sendersKeys)
|
||||
throws MissingOpenPgpKeyPairException, MissingOpenPgpPublicKeyException;
|
||||
|
||||
/**
|
||||
* Sign a {@link SignElement} and pack it inside a {@link OpenPgpElement}.
|
||||
* The resulting {@link OpenPgpElement} contains the {@link SignElement} signed and base64 encoded.
|
||||
* <br>
|
||||
* Note: DO NOT use this method in the context of instant messaging, as XEP-0374 forbids that.
|
||||
* Sign a {@link SignElement} with th users signing key.
|
||||
* The resulting byte array contains the signed byte representation of the {@link SignElement}.
|
||||
*
|
||||
* @see <a href="https://xmpp.org/extensions/xep-0373.html#exchange">XEP-0373 §3.1</a>
|
||||
* @see <a href="https://xmpp.org/extensions/xep-0374.html#openpgp-secured-im">XEP-0374 §2.1</a>
|
||||
*
|
||||
* @param element {@link SignElement} which will be signed.
|
||||
* @param singingKeyFingerprint {@link OpenPgpV4Fingerprint} of the key that is used for signing.
|
||||
* @return {@link OpenPgpElement} which contains the signed, Base64 encoded {@link SignElement}.
|
||||
* @return byte array which contains the signed {@link SignElement}.
|
||||
*
|
||||
* @throws MissingOpenPgpKeyPairException if we don't have the key pair for the
|
||||
* {@link OpenPgpV4Fingerprint} available.
|
||||
*/
|
||||
OpenPgpElement sign(SignElement element, OpenPgpV4Fingerprint singingKeyFingerprint)
|
||||
throws MissingOpenPgpKeyPairException;
|
||||
byte[] sign(SignElement element, OpenPgpV4Fingerprint singingKeyFingerprint)
|
||||
throws MissingOpenPgpKeyPairException, IOException, SmackOpenPgpException;
|
||||
|
||||
/**
|
||||
* Verify the signature on an incoming {@link OpenPgpElement} which must contain a {@link SignElement}.
|
||||
* <br>
|
||||
* Note: DO NOT use this method in the context of instant messaging, as XEP-0374 forbids that.
|
||||
*
|
||||
* @see <a href="https://xmpp.org/extensions/xep-0373.html#exchange">XEP-0373 §3.1</a>
|
||||
* @see <a href="https://xmpp.org/extensions/xep-0374.html#openpgp-secured-im">XEP-0374 §2.1</a>
|
||||
* @param element incoming {@link OpenPgpElement} which must contain a signed {@link SignElement}.
|
||||
* @param singingKeyFingerprints {@link Set} of the senders key {@link OpenPgpV4Fingerprint}s.
|
||||
* It is required that one of those keys was used to sign
|
||||
* the message.
|
||||
* @return {@link OpenPgpMessage} which contains the decoded {@link SignElement}.
|
||||
* @throws MissingOpenPgpPublicKeyException if we don't have the signers public key which signed
|
||||
* the message available.
|
||||
*/
|
||||
OpenPgpMessage verify(OpenPgpElement element, Set<OpenPgpV4Fingerprint> singingKeyFingerprints)
|
||||
throws MissingOpenPgpPublicKeyException;
|
||||
|
||||
/**
|
||||
* Encrypt a {@link CryptElement} and pack it inside a {@link OpenPgpElement}.
|
||||
* The resulting {@link OpenPgpElement} contains the encrypted and Base64 encoded {@link CryptElement}
|
||||
* Encrypt a {@link CryptElement} for all keys which fingerprints are contained in
|
||||
* {@code encryptionKeyFingerprints}.
|
||||
* The resulting byte array contains the encrypted {@link CryptElement}
|
||||
* which can be decrypted by all recipients, as well as by ourselves.
|
||||
* <br>
|
||||
* Note: DO NOT use this method in the context of instant messaging, as XEP-0374 forbids that.
|
||||
*
|
||||
* @see <a href="https://xmpp.org/extensions/xep-0374.html#openpgp-secured-im">XEP-0374 §2.1</a>
|
||||
*
|
||||
* @param element plaintext {@link CryptElement} which will be encrypted.
|
||||
* @param encryptionKeyFingerprints {@link Set} of {@link OpenPgpV4Fingerprint}s of the keys which
|
||||
* are used for encryption.
|
||||
* @return {@link OpenPgpElement} which contains the encrypted, Base64 encoded {@link CryptElement}.
|
||||
* @param encryptionKeyFingerprints {@link MultiMap} of recipients and {@link OpenPgpV4Fingerprint}s of the
|
||||
* keys which are used for encryption.
|
||||
* @return byte array which contains the encrypted {@link CryptElement}.
|
||||
* @throws MissingOpenPgpPublicKeyException if any of the OpenPGP public keys whose
|
||||
* {@link OpenPgpV4Fingerprint} is listed in {@code encryptionKeys}
|
||||
* is not available.
|
||||
*/
|
||||
OpenPgpElement encrypt(CryptElement element, Set<OpenPgpV4Fingerprint> encryptionKeyFingerprints)
|
||||
throws MissingOpenPgpPublicKeyException;
|
||||
byte[] encrypt(CryptElement element, MultiMap<BareJid, OpenPgpV4Fingerprint> encryptionKeyFingerprints)
|
||||
throws MissingOpenPgpPublicKeyException, IOException, SmackOpenPgpException;
|
||||
|
||||
/**
|
||||
* Decrypt an incoming {@link OpenPgpElement} which must contain a {@link CryptElement}.
|
||||
* The resulting {@link OpenPgpMessage} will contain the decrypted {@link CryptElement}.
|
||||
* <br>
|
||||
* Note: DO NOT use this method in the context of instant messaging, as XEP-0374 forbids that.
|
||||
* Process an incoming {@link OpenPgpElement}.
|
||||
* If its content is encrypted ({@link CryptElement} or {@link SigncryptElement}), the content will be decrypted.
|
||||
* If its content is signed ({@link SignElement} or {@link SigncryptElement}), signatures are verified using
|
||||
* the announced public keys of the sender.
|
||||
* The resulting byte array will contain the decrypted {@link OpenPgpContentElement}.
|
||||
*
|
||||
* @see <a href="https://xmpp.org/extensions/xep-0373.html#exchange">XEP-0373 §3.1</a>
|
||||
*
|
||||
* @param bytes byte array which contains the encrypted {@link OpenPgpContentElement}.
|
||||
* @return byte array which contains the decrypted {@link OpenPgpContentElement}, as well as metadata.
|
||||
*
|
||||
* @see <a href="https://xmpp.org/extensions/xep-0374.html#openpgp-secured-im">XEP-0374 §2.1</a>
|
||||
* @param element {@link OpenPgpElement} which contains the encrypted {@link CryptElement}.
|
||||
* @return {@link OpenPgpMessage} which contains the decrypted {@link CryptElement}.
|
||||
* @throws MissingOpenPgpKeyPairException if we don't have an OpenPGP key pair available that to decrypt
|
||||
* the message.
|
||||
*/
|
||||
OpenPgpMessage decrypt(OpenPgpElement element)
|
||||
throws MissingOpenPgpKeyPairException;
|
||||
DecryptedBytesAndMetadata decrypt(byte[] bytes, BareJid sender, SmackMissingOpenPgpPublicKeyCallback missingPublicKeyCallback)
|
||||
throws MissingOpenPgpKeyPairException, SmackOpenPgpException, IOException;
|
||||
|
||||
byte[] symmetricallyEncryptWithPassword(byte[] bytes, String password) throws SmackOpenPgpException, IOException;
|
||||
|
||||
byte[] symmetricallyDecryptWithPassword(byte[] bytes, String password) throws SmackOpenPgpException, IOException;
|
||||
|
||||
/**
|
||||
* Generate a fresh OpenPGP key pair.
|
||||
*
|
||||
* @param owner JID of the keys owner.
|
||||
* @return byte array representation + {@link OpenPgpV4Fingerprint} of the generated key pair.
|
||||
*/
|
||||
KeyBytesAndFingerprint generateOpenPgpKeyPair(BareJid owner)
|
||||
throws SmackOpenPgpException, InvalidAlgorithmParameterException, NoSuchAlgorithmException,
|
||||
NoSuchProviderException, IOException;
|
||||
|
||||
OpenPgpV4Fingerprint importPublicKey(BareJid owner, byte[] bytes) throws MissingUserIdOnKeyException;
|
||||
|
||||
OpenPgpV4Fingerprint importSecretKey(BareJid owner, byte[] bytes) throws MissingUserIdOnKeyException;
|
||||
|
||||
OpenPgpStore getStore();
|
||||
}
|
||||
|
|
|
@ -16,19 +16,11 @@
|
|||
*/
|
||||
package org.jivesoftware.smackx.ox;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jivesoftware.smack.XMPPConnection;
|
||||
import org.jivesoftware.smackx.ox.callback.SecretKeyRestoreSelectionCallback;
|
||||
import org.jivesoftware.smackx.ox.element.PubkeyElement;
|
||||
import org.jivesoftware.smackx.ox.element.PublicKeysListElement;
|
||||
import org.jivesoftware.smackx.ox.element.SecretkeyElement;
|
||||
import org.jivesoftware.smackx.ox.element.SigncryptElement;
|
||||
import org.jivesoftware.smackx.ox.exception.InvalidBackupCodeException;
|
||||
import org.jivesoftware.smack.util.MultiMap;
|
||||
import org.jivesoftware.smackx.ox.exception.MissingOpenPgpKeyPairException;
|
||||
import org.jivesoftware.smackx.ox.exception.MissingOpenPgpPublicKeyException;
|
||||
import org.jivesoftware.smackx.ox.exception.SmackOpenPgpException;
|
||||
|
@ -45,123 +37,105 @@ public interface OpenPgpStore {
|
|||
*
|
||||
* @return fingerprint of the primary OpenPGP key pair.
|
||||
*/
|
||||
OpenPgpV4Fingerprint primaryOpenPgpKeyPairFingerprint();
|
||||
OpenPgpV4Fingerprint getPrimaryOpenPgpKeyPairFingerprint();
|
||||
|
||||
/**
|
||||
* Return a {@link Set} containing the {@link OpenPgpV4Fingerprint} of all available OpenPGP key pairs.
|
||||
* Set the {@link OpenPgpV4Fingerprint} of the primary OpenPGP key pair.
|
||||
* If multiple key pairs are available, only the primary key pair is used for signing.
|
||||
*
|
||||
* @return set of fingerprints of available OpenPGP key pairs.
|
||||
* @param fingerprint {@link OpenPgpV4Fingerprint} of the new primary key pair.
|
||||
*/
|
||||
Set<OpenPgpV4Fingerprint> availableOpenPgpKeyPairFingerprints();
|
||||
void setPrimaryOpenPgpKeyPairFingerprint(OpenPgpV4Fingerprint fingerprint);
|
||||
|
||||
/**
|
||||
* Return a {@link Set} containing the {@link OpenPgpV4Fingerprint}s of the master keys of all available
|
||||
* OpenPGP key pairs.
|
||||
*
|
||||
* @return set of fingerprints of available OpenPGP key pairs master keys.
|
||||
*/
|
||||
Set<OpenPgpV4Fingerprint> getAvailableKeyPairFingerprints();
|
||||
|
||||
/**
|
||||
* Return a {@link Map} containing the {@link OpenPgpV4Fingerprint}s of all OpenPGP public keys of a
|
||||
* contact, which we have locally available, as well as the date, those keys had been published on.
|
||||
* <br>
|
||||
* Note: This returns a {@link Map} that might be different from the result of
|
||||
* {@link #getAvailableKeysFingerprints(BareJid)} (BareJid)}.
|
||||
* Messages should be encrypted to the intersection of both key sets.
|
||||
*
|
||||
* @param contact contact.
|
||||
* @return list of contacts locally available public keys.
|
||||
*
|
||||
* @throws SmackOpenPgpException if something goes wrong
|
||||
*/
|
||||
Map<OpenPgpV4Fingerprint, Date> getAvailableKeysFingerprints(BareJid contact)
|
||||
throws SmackOpenPgpException;
|
||||
|
||||
/**
|
||||
* Return a {@link Map} containing the {@link OpenPgpV4Fingerprint}s of all currently announced OpenPGP
|
||||
* public keys of a contact along with the dates of their latest update.
|
||||
* public keys of a contact along with the dates of their latest revision.
|
||||
* <br>
|
||||
* Note: Those are the keys announced in the latest received metadata update.
|
||||
* This returns a {@link Map} which might contain different {@link OpenPgpV4Fingerprint}s than the result of
|
||||
* {@link #availableOpenPgpPublicKeysFingerprints(BareJid)}.
|
||||
* Messages should be encrypted to the intersection of both sets.
|
||||
* {@link #getAvailableKeysFingerprints(BareJid)} (BareJid)}.
|
||||
* Messages should be encrypted to the intersection of both key sets.
|
||||
*
|
||||
* @param contact contact.
|
||||
* @return map of contacts last announced public keys and their update dates.
|
||||
*/
|
||||
Map<OpenPgpV4Fingerprint, Date> announcedOpenPgpKeyFingerprints(BareJid contact);
|
||||
Map<OpenPgpV4Fingerprint, Date> getAnnouncedKeysFingerprints(BareJid contact);
|
||||
|
||||
/**
|
||||
* Return a {@link Set} containing the {@link OpenPgpV4Fingerprint}s of all OpenPGP public keys of a
|
||||
* contact, which we have locally available.
|
||||
* <br>
|
||||
* Note: This returns a {@link Set} that might be different from the result of
|
||||
* {@link #availableOpenPgpPublicKeysFingerprints(BareJid)}.
|
||||
* Messages should be encrypted to the intersection of both sets.
|
||||
* Store a {@Map} of a contacts fingerprints and publication dates in persistent storage.
|
||||
*
|
||||
* @param contact contact.
|
||||
* @return list of contacts locally available public keys.
|
||||
* @throws SmackOpenPgpException if something goes wrong
|
||||
* @param contact {@link BareJid} of the owner of the announced public keys.
|
||||
* @param fingerprints {@link Map} which contains a list of the keys of {@code owner}.
|
||||
*/
|
||||
Set<OpenPgpV4Fingerprint> availableOpenPgpPublicKeysFingerprints(BareJid contact)
|
||||
throws SmackOpenPgpException;
|
||||
void setAnnouncedKeysFingerprints(BareJid contact, Map<OpenPgpV4Fingerprint, Date> fingerprints);
|
||||
|
||||
/**
|
||||
* Store incoming update to the OpenPGP metadata node in persistent storage.
|
||||
*
|
||||
* @param connection authenticated {@link XMPPConnection} of the user.
|
||||
* @param listElement {@link PublicKeysListElement} which contains a list of the keys of {@code owner}.
|
||||
* @param owner {@link BareJid} of the owner of the announced public keys.
|
||||
*/
|
||||
void storePublicKeysList(XMPPConnection connection, PublicKeysListElement listElement, BareJid owner);
|
||||
|
||||
/**
|
||||
* Create a fresh OpenPGP key pair with the {@link BareJid} of the user prefixed by "xmpp:" as user-id
|
||||
* (example: {@code "xmpp:juliet@capulet.lit"}).
|
||||
* Store the key pair in persistent storage and return the public keys {@link OpenPgpV4Fingerprint}.
|
||||
*
|
||||
* @return {@link OpenPgpV4Fingerprint} of the generated key pair.
|
||||
* @throws NoSuchAlgorithmException if a Hash algorithm is not available
|
||||
* @throws NoSuchProviderException id no suitable cryptographic provider (for example BouncyCastleProvider)
|
||||
* is registered.
|
||||
* @throws SmackOpenPgpException if the generated key cannot be added to the keyring for some reason.
|
||||
*/
|
||||
OpenPgpV4Fingerprint createOpenPgpKeyPair()
|
||||
throws NoSuchAlgorithmException, NoSuchProviderException, SmackOpenPgpException;
|
||||
|
||||
/**
|
||||
* Create a {@link PubkeyElement} which contains our exported OpenPGP public key.
|
||||
* The element can for example be published.
|
||||
*
|
||||
* @return {@link PubkeyElement} containing our public key.
|
||||
* @throws MissingOpenPgpPublicKeyException if we have no OpenPGP key pair.
|
||||
* @throws SmackOpenPgpException if something goes wrong.
|
||||
*/
|
||||
PubkeyElement createPubkeyElement(OpenPgpV4Fingerprint fingerprint)
|
||||
throws SmackOpenPgpException, MissingOpenPgpPublicKeyException;
|
||||
|
||||
/**
|
||||
* Process an incoming {@link PubkeyElement} of a contact or ourselves.
|
||||
* That typically includes importing/updating the key.
|
||||
*
|
||||
* @param owner owner of the OpenPGP public key contained in the {@link PubkeyElement}.
|
||||
* @param fingerprint {@link OpenPgpV4Fingerprint} of the key.
|
||||
* @param element {@link PubkeyElement} which presumably contains the public key of the {@code owner}.
|
||||
* @param currentMetadataDate {@link Date} which is currently found in the metadata node for this key.
|
||||
* @throws SmackOpenPgpException if the key found in the {@link PubkeyElement}
|
||||
* can not be deserialized or imported.
|
||||
*/
|
||||
void storePublicKey(BareJid owner, OpenPgpV4Fingerprint fingerprint, PubkeyElement element, Date currentMetadataDate)
|
||||
throws SmackOpenPgpException;
|
||||
|
||||
/**
|
||||
* Return the {@link Date} of the last time on which the key has been fetched from PubSub.
|
||||
* Return the {@link Date} of the last revision which was fetched from PubSub.
|
||||
*
|
||||
* @param owner owner of the key
|
||||
* @param fingerprint fingerprint of the key.
|
||||
* @return {@link Date} or {@code null} if no record found.
|
||||
*/
|
||||
Date getPubkeysLatestUpdateDate(BareJid owner, OpenPgpV4Fingerprint fingerprint);
|
||||
Date getPubkeysLastRevision(BareJid owner, OpenPgpV4Fingerprint fingerprint);
|
||||
|
||||
/**
|
||||
* Create an encrypted backup of our secret keys.
|
||||
* Set the {@link Date} of the last revision which was fetched from PubSub.
|
||||
*
|
||||
* @param fingerprints {@link Set} of IDs of the keys that will be included in the backup.
|
||||
* @param password password that is used to symmetrically encrypt the backup.
|
||||
* @return {@link SigncryptElement} containing the selected encrypted secret keys.
|
||||
* @throws MissingOpenPgpKeyPairException if we don't have an OpenPGP key available.
|
||||
* @throws SmackOpenPgpException if for some reason the key pair cannot be serialized.
|
||||
* @param owner owner of the key
|
||||
* @param fingerprint fingerprint of the key
|
||||
* @param revision {@link Date} of the revision
|
||||
*/
|
||||
SecretkeyElement createSecretkeyElement(Set<OpenPgpV4Fingerprint> fingerprints, String password)
|
||||
throws MissingOpenPgpKeyPairException, SmackOpenPgpException;
|
||||
void setPubkeysLastRevision(BareJid owner, OpenPgpV4Fingerprint fingerprint, Date revision);
|
||||
|
||||
/**
|
||||
* Decrypt a secret key backup and restore the key from it.
|
||||
* Return a {@link MultiMap} which contains contacts and their trusted keys {@link OpenPgpV4Fingerprint}s.
|
||||
*
|
||||
* @param secretkeyElement {@link SecretkeyElement} containing the backup.
|
||||
* @param password password to decrypt the backup.
|
||||
* @param callback {@link SecretKeyRestoreSelectionCallback} to let the user decide which key to restore.
|
||||
* @throws SmackOpenPgpException if the selected key is corrupted and cannot be restored or our key ring
|
||||
* is corrupted.
|
||||
* @throws InvalidBackupCodeException if the user provided backup code is invalid.
|
||||
* @return trusted fingerprints.
|
||||
*/
|
||||
void restoreSecretKeyBackup(SecretkeyElement secretkeyElement, String password, SecretKeyRestoreSelectionCallback callback)
|
||||
throws SmackOpenPgpException, InvalidBackupCodeException;
|
||||
MultiMap<BareJid, OpenPgpV4Fingerprint> getAllContactsTrustedFingerprints();
|
||||
|
||||
/**
|
||||
* Return the byte array representation of {@code owner}s public key ring with fingerprint {@code fingerprint}.
|
||||
*
|
||||
* @param owner owner of the key
|
||||
* @param fingerprint fingerprint of the key
|
||||
* @return byte representation of the public key.
|
||||
*/
|
||||
byte[] getPublicKeyBytes(BareJid owner, OpenPgpV4Fingerprint fingerprint)
|
||||
throws MissingOpenPgpPublicKeyException;
|
||||
|
||||
/**
|
||||
* Return the byte array representation of {@code owner}s secret key ring with fingerprint {@code fingerprint}.
|
||||
*
|
||||
* @param owner owner of the key
|
||||
* @param fingerprint fingerprint of the key
|
||||
* @return byte representation of the secret key.
|
||||
*/
|
||||
byte[] getSecretKeyBytes(BareJid owner, OpenPgpV4Fingerprint fingerprint)
|
||||
throws MissingOpenPgpKeyPairException;
|
||||
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.jivesoftware.smackx.ox;
|
|||
import java.nio.charset.Charset;
|
||||
|
||||
import org.jivesoftware.smack.util.Objects;
|
||||
import org.jivesoftware.smackx.ox.util.Util;
|
||||
|
||||
/**
|
||||
* This class represents an hex encoded, uppercase OpenPGP v4 fingerprint.
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
package org.jivesoftware.smackx.ox.callback;
|
||||
|
||||
public interface SmackMissingOpenPgpPublicKeyCallback {
|
||||
}
|
|
@ -14,25 +14,30 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jivesoftware.smackx.ox;
|
||||
package org.jivesoftware.smackx.ox.chat;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jivesoftware.smack.SmackException;
|
||||
import org.jivesoftware.smack.chat2.Chat;
|
||||
import org.jivesoftware.smack.packet.ExtensionElement;
|
||||
import org.jivesoftware.smack.packet.Message;
|
||||
import org.jivesoftware.smack.util.MultiMap;
|
||||
import org.jivesoftware.smack.util.stringencoder.Base64;
|
||||
import org.jivesoftware.smackx.eme.element.ExplicitMessageEncryptionElement;
|
||||
import org.jivesoftware.smackx.hints.element.StoreHint;
|
||||
import org.jivesoftware.smackx.ox.OpenPgpProvider;
|
||||
import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint;
|
||||
import org.jivesoftware.smackx.ox.element.OpenPgpElement;
|
||||
import org.jivesoftware.smackx.ox.element.SigncryptElement;
|
||||
import org.jivesoftware.smackx.ox.exception.MissingOpenPgpKeyPairException;
|
||||
import org.jivesoftware.smackx.ox.exception.MissingOpenPgpPublicKeyException;
|
||||
import org.jivesoftware.smackx.ox.exception.SmackOpenPgpException;
|
||||
|
||||
import org.jxmpp.jid.BareJid;
|
||||
import org.jxmpp.jid.Jid;
|
||||
|
||||
public class OpenPgpEncryptedChat {
|
||||
|
@ -49,31 +54,35 @@ public class OpenPgpEncryptedChat {
|
|||
OpenPgpFingerprints contactsFingerprints) {
|
||||
this.cryptoProvider = cryptoProvider;
|
||||
this.chat = chat;
|
||||
this.singingKey = cryptoProvider.primaryOpenPgpKeyPairFingerprint();
|
||||
this.singingKey = cryptoProvider.getStore().getPrimaryOpenPgpKeyPairFingerprint();
|
||||
this.ourFingerprints = ourFingerprints;
|
||||
this.contactsFingerprints = contactsFingerprints;
|
||||
}
|
||||
|
||||
public void send(Message message, List<ExtensionElement> payload)
|
||||
throws MissingOpenPgpKeyPairException, SmackException.NotConnectedException, InterruptedException {
|
||||
Set<OpenPgpV4Fingerprint> encryptionFingerprints = new HashSet<>(contactsFingerprints.getActiveKeys());
|
||||
encryptionFingerprints.addAll(ourFingerprints.getActiveKeys());
|
||||
throws MissingOpenPgpKeyPairException, SmackException.NotConnectedException, InterruptedException,
|
||||
SmackOpenPgpException, IOException {
|
||||
MultiMap<BareJid, OpenPgpV4Fingerprint> fingerprints = oursAndRecipientFingerprints();
|
||||
|
||||
SigncryptElement preparedPayload = new SigncryptElement(
|
||||
Collections.<Jid>singleton(chat.getXmppAddressOfChatPartner()),
|
||||
payload);
|
||||
|
||||
OpenPgpElement encryptedPayload;
|
||||
byte[] encryptedMessage;
|
||||
|
||||
// Encrypt the payload
|
||||
try {
|
||||
encryptedPayload = cryptoProvider.signAndEncrypt(
|
||||
encryptedMessage = cryptoProvider.signAndEncrypt(
|
||||
preparedPayload,
|
||||
singingKey,
|
||||
encryptionFingerprints);
|
||||
fingerprints);
|
||||
} catch (MissingOpenPgpPublicKeyException e) {
|
||||
throw new AssertionError("Missing OpenPGP public key, even though this should not happen here.", e);
|
||||
}
|
||||
|
||||
encryptedPayload = new OpenPgpElement(Base64.encodeToString(encryptedMessage));
|
||||
|
||||
// Add encrypted payload to message
|
||||
message.addExtension(encryptedPayload);
|
||||
|
||||
|
@ -87,9 +96,25 @@ public class OpenPgpEncryptedChat {
|
|||
}
|
||||
|
||||
public void send(Message message, CharSequence body)
|
||||
throws MissingOpenPgpKeyPairException, SmackException.NotConnectedException, InterruptedException {
|
||||
throws MissingOpenPgpKeyPairException, SmackException.NotConnectedException, InterruptedException,
|
||||
SmackOpenPgpException, IOException {
|
||||
List<ExtensionElement> payload = new ArrayList<>();
|
||||
payload.add(new Message.Body(null, body.toString()));
|
||||
send(message, payload);
|
||||
}
|
||||
|
||||
private MultiMap<BareJid, OpenPgpV4Fingerprint> oursAndRecipientFingerprints() {
|
||||
MultiMap<BareJid, OpenPgpV4Fingerprint> fingerprints = new MultiMap<>();
|
||||
for (OpenPgpV4Fingerprint f : contactsFingerprints.getActiveKeys()) {
|
||||
fingerprints.put(contactsFingerprints.getJid(), f);
|
||||
}
|
||||
|
||||
if (!contactsFingerprints.getJid().equals(ourFingerprints.getJid())) {
|
||||
for (OpenPgpV4Fingerprint f : ourFingerprints.getActiveKeys()) {
|
||||
fingerprints.put(ourFingerprints.getJid(), f);
|
||||
}
|
||||
}
|
||||
|
||||
return fingerprints;
|
||||
}
|
||||
}
|
|
@ -14,13 +14,15 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jivesoftware.smackx.ox;
|
||||
package org.jivesoftware.smackx.ox.chat;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint;
|
||||
|
||||
import org.jxmpp.jid.BareJid;
|
||||
|
||||
/**
|
||||
|
@ -28,6 +30,7 @@ import org.jxmpp.jid.BareJid;
|
|||
*/
|
||||
public class OpenPgpFingerprints {
|
||||
|
||||
private final BareJid jid;
|
||||
private final Set<OpenPgpV4Fingerprint> announcedKeys;
|
||||
private final Set<OpenPgpV4Fingerprint> availableKeys;
|
||||
private final Map<OpenPgpV4Fingerprint, Throwable> unfetchableKeys;
|
||||
|
@ -39,14 +42,24 @@ public class OpenPgpFingerprints {
|
|||
* @param availableKeys keys which contain the contacts {@link BareJid} as user ID.
|
||||
* @param unfetchableKeys keys that are announced, but cannot be fetched fro PubSub.
|
||||
*/
|
||||
public OpenPgpFingerprints(Set<OpenPgpV4Fingerprint> announcedKeys,
|
||||
public OpenPgpFingerprints(BareJid jid,
|
||||
Set<OpenPgpV4Fingerprint> announcedKeys,
|
||||
Set<OpenPgpV4Fingerprint> availableKeys,
|
||||
Map<OpenPgpV4Fingerprint, Throwable> unfetchableKeys) {
|
||||
this.jid = jid;
|
||||
this.announcedKeys = Collections.unmodifiableSet(announcedKeys);
|
||||
this.availableKeys = Collections.unmodifiableSet(availableKeys);
|
||||
this.unfetchableKeys = Collections.unmodifiableMap(unfetchableKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link BareJid} of the user.
|
||||
* @return jid
|
||||
*/
|
||||
public BareJid getJid() {
|
||||
return jid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link Set} of {@link OpenPgpV4Fingerprint}s, which the contact in question announced via their
|
||||
* metadata node.
|
|
@ -14,10 +14,15 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jivesoftware.smackx.ox;
|
||||
package org.jivesoftware.smackx.ox.chat;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jivesoftware.smack.util.Objects;
|
||||
import org.jivesoftware.smack.util.stringencoder.Base64;
|
||||
import org.jivesoftware.smackx.ox.element.CryptElement;
|
||||
import org.jivesoftware.smackx.ox.element.OpenPgpContentElement;
|
||||
import org.jivesoftware.smackx.ox.element.OpenPgpElement;
|
||||
|
@ -49,7 +54,7 @@ public class OpenPgpMessage {
|
|||
}
|
||||
|
||||
private final String element;
|
||||
private State state;
|
||||
private final State state;
|
||||
|
||||
private OpenPgpContentElement openPgpContentElement;
|
||||
|
||||
|
@ -60,8 +65,13 @@ public class OpenPgpMessage {
|
|||
* @param content XML representation of the decrypted {@link OpenPgpContentElement}.
|
||||
*/
|
||||
public OpenPgpMessage(State state, String content) {
|
||||
this.state = state;
|
||||
this.element = content;
|
||||
this.state = Objects.requireNonNull(state);
|
||||
this.element = Objects.requireNonNull(content);
|
||||
}
|
||||
|
||||
public OpenPgpMessage(byte[] bytes, Metadata metadata) {
|
||||
this.element = new String(Base64.decode(bytes), Charset.forName("UTF-8"));
|
||||
this.state = metadata.getState();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -90,14 +100,17 @@ public class OpenPgpMessage {
|
|||
|
||||
// Determine the state of the content element.
|
||||
if (openPgpContentElement instanceof SigncryptElement) {
|
||||
state = State.signcrypt;
|
||||
if (state != State.signcrypt) {
|
||||
throw new IllegalStateException("OpenPgpContentElement was signed and encrypted, but is not a SigncryptElement.");
|
||||
}
|
||||
} else if (openPgpContentElement instanceof SignElement) {
|
||||
state = State.sign;
|
||||
if (state != State.sign) {
|
||||
throw new IllegalStateException("OpenPgpContentElement was signed and unencrypted, but is not a SignElement.");
|
||||
}
|
||||
} else if (openPgpContentElement instanceof CryptElement) {
|
||||
state = State.crypt;
|
||||
} else {
|
||||
throw new AssertionError("OpenPgpContentElement is neither a SignElement, " +
|
||||
"CryptElement nor a SignCryptElement.");
|
||||
if (state != State.crypt) {
|
||||
throw new IllegalStateException("OpenPgpContentElement was unsigned and encrypted, but is not a CryptElement.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -113,4 +126,40 @@ public class OpenPgpMessage {
|
|||
ensureOpenPgpContentElementSet();
|
||||
return state;
|
||||
}
|
||||
|
||||
public static class Metadata {
|
||||
|
||||
private final Long encryptionKeyId;
|
||||
private final Set<Long> validSignatureIds;
|
||||
|
||||
public Metadata(Long encryptionKeyId, Set<Long> validSignatureIds) {
|
||||
this.encryptionKeyId = encryptionKeyId;
|
||||
this.validSignatureIds = validSignatureIds;
|
||||
}
|
||||
|
||||
public Long getEncryptionKeyId() {
|
||||
return encryptionKeyId;
|
||||
}
|
||||
|
||||
public Set<Long> getValidSignatureIds() {
|
||||
return new HashSet<>(validSignatureIds);
|
||||
}
|
||||
|
||||
public State getState() {
|
||||
if (validSignatureIds.size() != 0) {
|
||||
if (encryptionKeyId != null) {
|
||||
return State.signcrypt;
|
||||
} else {
|
||||
return State.sign;
|
||||
}
|
||||
} else {
|
||||
if (encryptionKeyId != null) {
|
||||
return State.crypt;
|
||||
} else {
|
||||
throw new IllegalStateException("OpenPGP message appears to be neither encrypted, " +
|
||||
"nor signed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -31,8 +31,9 @@ public class MissingOpenPgpKeyPairException extends Exception {
|
|||
* Create a new {@link MissingOpenPgpKeyPairException}.
|
||||
*
|
||||
* @param owner owner of the missing key pair.
|
||||
* @param e
|
||||
*/
|
||||
public MissingOpenPgpKeyPairException(BareJid owner) {
|
||||
public MissingOpenPgpKeyPairException(BareJid owner, Throwable e) {
|
||||
super("Missing OpenPGP key pair for user " + owner);
|
||||
this.owner = owner;
|
||||
}
|
||||
|
|
|
@ -27,8 +27,8 @@ public class MissingOpenPgpPublicKeyException extends Exception {
|
|||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final BareJid user;
|
||||
private final OpenPgpV4Fingerprint fingerprint;
|
||||
private BareJid user;
|
||||
private OpenPgpV4Fingerprint fingerprint;
|
||||
|
||||
/**
|
||||
* Create a new {@link MissingOpenPgpPublicKeyException}.
|
||||
|
@ -42,6 +42,10 @@ public class MissingOpenPgpPublicKeyException extends Exception {
|
|||
this.fingerprint = fingerprint;
|
||||
}
|
||||
|
||||
public MissingOpenPgpPublicKeyException(Throwable e) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link BareJid} of the owner of the missing key.
|
||||
*
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
package org.jivesoftware.smackx.ox.exception;
|
||||
|
||||
import org.jxmpp.jid.BareJid;
|
||||
|
||||
public class MissingUserIdOnKeyException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public MissingUserIdOnKeyException(BareJid owner, long keyId) {
|
||||
super("Key " + Long.toHexString(keyId) + " does not have a user-id of \"xmpp:" + owner.toString() + "\".");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
package org.jivesoftware.smackx.ox.exception;
|
||||
|
||||
public class NoBackupFoundException {
|
||||
}
|
|
@ -17,7 +17,7 @@
|
|||
package org.jivesoftware.smackx.ox.listener;
|
||||
|
||||
import org.jivesoftware.smack.packet.Message;
|
||||
import org.jivesoftware.smackx.ox.OpenPgpEncryptedChat;
|
||||
import org.jivesoftware.smackx.ox.chat.OpenPgpEncryptedChat;
|
||||
import org.jivesoftware.smackx.ox.element.OpenPgpElement;
|
||||
import org.jivesoftware.smackx.ox.element.SigncryptElement;
|
||||
|
||||
|
@ -36,8 +36,8 @@ public interface OpenPgpEncryptedMessageListener {
|
|||
* @param decryptedPayload decrypted {@link SigncryptElement} which is carrying the payload.
|
||||
* @param chat {@link OpenPgpEncryptedChat} which is the context of the message.
|
||||
*/
|
||||
void newIncomingEncryptedMessage(EntityBareJid from,
|
||||
Message originalMessage,
|
||||
SigncryptElement decryptedPayload,
|
||||
OpenPgpEncryptedChat chat);
|
||||
void newIncomingOxMessage(EntityBareJid from,
|
||||
Message originalMessage,
|
||||
SigncryptElement decryptedPayload,
|
||||
OpenPgpEncryptedChat chat);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
package org.jivesoftware.smackx.ox.util;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class DecryptedBytesAndMetadata {
|
||||
|
||||
private final byte[] bytes;
|
||||
private final Set<Long> verifiedSignatures;
|
||||
private final Long decryptionKey;
|
||||
|
||||
public DecryptedBytesAndMetadata(byte[] bytes, Set<Long> verifiedSignatures, Long decryptionKey) {
|
||||
this.bytes = bytes;
|
||||
this.verifiedSignatures = verifiedSignatures;
|
||||
this.decryptionKey = decryptionKey;
|
||||
}
|
||||
|
||||
public byte[] getBytes() {
|
||||
return Arrays.copyOf(bytes, bytes.length);
|
||||
}
|
||||
|
||||
public Long getDecryptionKey() {
|
||||
return decryptionKey;
|
||||
}
|
||||
|
||||
public Set<Long> getVerifiedSignatures() {
|
||||
return new HashSet<>(verifiedSignatures);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package org.jivesoftware.smackx.ox.util;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint;
|
||||
|
||||
public class KeyBytesAndFingerprint {
|
||||
|
||||
private final byte[] bytes;
|
||||
private final OpenPgpV4Fingerprint fingerprint;
|
||||
|
||||
public KeyBytesAndFingerprint(byte[] bytes, OpenPgpV4Fingerprint fingerprint) {
|
||||
this.bytes = bytes;
|
||||
this.fingerprint = fingerprint;
|
||||
}
|
||||
|
||||
public byte[] getBytes() {
|
||||
return Arrays.copyOf(bytes, bytes.length);
|
||||
}
|
||||
|
||||
public OpenPgpV4Fingerprint getFingerprint() {
|
||||
return fingerprint;
|
||||
}
|
||||
}
|
|
@ -14,7 +14,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jivesoftware.smackx.ox;
|
||||
package org.jivesoftware.smackx.ox.util;
|
||||
|
||||
import org.jivesoftware.smack.initializer.UrlInitializer;
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jivesoftware.smackx.ox;
|
||||
package org.jivesoftware.smackx.ox.util;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
@ -25,6 +25,7 @@ import org.jivesoftware.smack.SmackException;
|
|||
import org.jivesoftware.smack.XMPPConnection;
|
||||
import org.jivesoftware.smack.XMPPException;
|
||||
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
|
||||
import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint;
|
||||
import org.jivesoftware.smackx.ox.element.PubkeyElement;
|
||||
import org.jivesoftware.smackx.ox.element.PublicKeysListElement;
|
||||
import org.jivesoftware.smackx.ox.element.SecretkeyElement;
|
||||
|
@ -72,7 +73,19 @@ public class PubSubDelegate {
|
|||
return PEP_NODE_PUBLIC_KEYS + ":" + id;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Query the access model of {@code node}. If it is different from {@code accessModel}, change the access model
|
||||
* of the node to {@code accessModel}.
|
||||
*
|
||||
* @see <a href="https://xmpp.org/extensions/xep-0060.html#accessmodels">XEP-0060 §4.5 - Node Access Models</a>
|
||||
*
|
||||
* @param node {@link LeafNode} whose PubSub access model we want to change
|
||||
* @param accessModel new access model.
|
||||
* @throws XMPPException.XMPPErrorException
|
||||
* @throws SmackException.NotConnectedException
|
||||
* @throws InterruptedException
|
||||
* @throws SmackException.NoResponseException
|
||||
*/
|
||||
public static void changeAccessModelIfNecessary(LeafNode node, AccessModel accessModel)
|
||||
throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
|
||||
SmackException.NoResponseException {
|
|
@ -14,12 +14,14 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jivesoftware.smackx.ox;
|
||||
package org.jivesoftware.smackx.ox.util;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
import javax.xml.bind.DatatypeConverter;
|
||||
|
||||
import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint;
|
||||
|
||||
public class Util {
|
||||
|
||||
/**
|
Loading…
Reference in a new issue