1
0
Fork 0
mirror of https://github.com/vanitasvitae/Smack.git synced 2024-11-23 20:42:06 +01:00

Get rid of smack-openpgp-bouncycastle

This commit is contained in:
Paul Schaub 2018-07-08 19:36:44 +02:00
parent 15eee8e65f
commit 06dbb37a0e
Signed by: vanitasvitae
GPG key ID: 62BEE9264BF17311
54 changed files with 218 additions and 2622 deletions

View file

@ -79,6 +79,7 @@ allprojects {
':smack-experimental',
':smack-omemo',
':smack-omemo-signal',
':smack-openpgp',
].collect{ project(it) }
androidBootClasspathProjects = [
':smack-android',

View file

@ -27,6 +27,5 @@ include 'smack-core',
'smack-omemo-signal',
'smack-omemo-signal-integration-test',
'smack-repl',
'smack-openpgp',
'smack-openpgp-bouncycastle'
'smack-openpgp'

View file

@ -13,7 +13,6 @@ dependencies {
compile project(':smack-experimental')
compile project(':smack-omemo')
compile project(':smack-openpgp')
compile project(':smack-openpgp-bouncycastle')
compile project(':smack-debug')
compile project(path: ":smack-omemo", configuration: "testRuntime")
compile 'org.reflections:reflections:0.9.9-RC1'

View file

@ -1,13 +0,0 @@
description = """\
Smack API for OpenPGP using Bouncycastle."""
// Note that the test dependencies (junit, ) are inferred from the
// sourceSet.test of the core subproject
dependencies {
compile project(':smack-core')
compile project(':smack-openpgp')
compile 'org.pgpainless: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")
}

View file

@ -1,108 +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.IOException;
import java.util.HashMap;
import java.util.Map;
import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint;
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;
import org.pgpainless.pgpainless.key.protection.SecretKeyRingProtector;
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<>();
private OpenPgpV4Fingerprint primaryKeyFingerprint = null;
private final SecretKeyRingProtector secretKeyRingProtector;
@Override
public OpenPgpV4Fingerprint getSigningKeyPairFingerprint() {
return primaryKeyFingerprint;
}
@Override
public void setSigningKeyPairFingerprint(OpenPgpV4Fingerprint fingerprint) {
this.primaryKeyFingerprint = fingerprint;
}
@Override
public SecretKeyRingProtector getSecretKeyProtector() {
return secretKeyRingProtector;
}
public AbstractPainlessOpenPgpStore(SecretKeyRingProtector secretKeyRingProtector) {
this.secretKeyRingProtector = secretKeyRingProtector;
}
@Override
public PGPPublicKeyRingCollection getPublicKeyRings(BareJid owner) throws IOException, PGPException {
PGPPublicKeyRingCollection keyRing = publicKeyRings.get(owner);
if (keyRing != null) {
return keyRing;
}
byte[] bytes = loadPublicKeyRingBytes(owner);
if (bytes == null) {
return null;
}
keyRing = new PGPPublicKeyRingCollection(bytes, fingerprintCalculator);
publicKeyRings.put(owner, keyRing);
return keyRing;
}
@Override
public PGPSecretKeyRingCollection getSecretKeyRings(BareJid owner) throws IOException, PGPException {
PGPSecretKeyRingCollection keyRing = secretKeyRings.get(owner);
if (keyRing != null) {
return keyRing;
}
byte[] bytes = loadSecretKeyRingBytes(owner);
if (bytes == null) {
return null;
}
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());
}
}

View file

@ -1,440 +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.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jivesoftware.smack.util.MultiMap;
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.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
import org.bouncycastle.util.io.Streams;
import org.jxmpp.jid.BareJid;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.util.XmppDateTime;
import org.pgpainless.pgpainless.key.protection.SecretKeyRingProtector;
public class FileBasedPainlessOpenPgpStore extends AbstractPainlessOpenPgpStore {
private static final Logger LOGGER = Logger.getLogger(FileBasedPainlessOpenPgpStore.class.getName());
private final File basePath;
public FileBasedPainlessOpenPgpStore(File base, SecretKeyRingProtector secretKeyRingProtector) {
super(secretKeyRingProtector);
this.basePath = base;
}
@Override
public byte[] loadPublicKeyRingBytes(BareJid owner) {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
FileInputStream inputStream = null;
byte[] bytes = null;
try {
inputStream = new FileInputStream(getContactsPubringFile(owner, false));
Streams.pipeAll(inputStream, buffer);
inputStream.close();
bytes = buffer.toByteArray();
} catch (FileNotFoundException e) {
LOGGER.log(Level.FINE, "Pubring of user " + owner.toString() + " does not exist.");
return null;
} catch (IOException e) {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException ee) {
LOGGER.log(Level.WARNING, "Could not close InputStream:", ee);
}
}
}
return bytes;
}
@Override
public byte[] loadSecretKeyRingBytes(BareJid owner) {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
FileInputStream inputStream = null;
byte[] bytes = null;
try {
inputStream = new FileInputStream(getContactsSecringFile(owner, false));
Streams.pipeAll(inputStream, buffer);
inputStream.close();
bytes = buffer.toByteArray();
} catch (FileNotFoundException e) {
LOGGER.log(Level.FINE, "Secring of user " + owner.toString() + " does not exist.");
return null;
} catch (IOException e) {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException ee) {
LOGGER.log(Level.WARNING, "Could not close InputStream:", ee);
}
}
}
return bytes;
}
@Override
public void storePublicKeyRingBytes(BareJid owner, byte[] bytes) {
FileOutputStream outputStream = null;
try {
outputStream = new FileOutputStream(getContactsPubringFile(owner, true));
outputStream.write(bytes);
outputStream.close();
} catch (FileNotFoundException e) {
throw new AssertionError("File does not exist, even though it MUST exist at this point.", e);
} catch (IOException e) {
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException ee) {
LOGGER.log(Level.WARNING, "Could not close OutputStream:", ee);
}
}
}
}
@Override
public void storeSecretKeyRingBytes(BareJid owner, byte[] bytes) {
FileOutputStream outputStream = null;
try {
outputStream = new FileOutputStream(getContactsSecringFile(owner, true));
outputStream.write(bytes);
outputStream.close();
} catch (FileNotFoundException e) {
throw new AssertionError("File does not exist, even though it MUST exist at this point.", e);
} catch (IOException e) {
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException ee) {
LOGGER.log(Level.WARNING, "Could not close OutputStream:", ee);
}
}
}
}
@Override
public Set<OpenPgpV4Fingerprint> getAvailableKeyPairFingerprints(BareJid owner) throws SmackOpenPgpException {
Set<OpenPgpV4Fingerprint> fingerprints = new HashSet<>();
try {
PGPSecretKeyRingCollection secretKeys = getSecretKeyRings(owner);
for (PGPSecretKeyRing s : secretKeys != null ? secretKeys : Collections.<PGPSecretKeyRing>emptySet()) {
fingerprints.add(PainlessOpenPgpProvider.getFingerprint(s.getPublicKey()));
}
} catch (IOException | PGPException e) {
throw new SmackOpenPgpException("Could not get available key pair fingerprints.", e);
}
return fingerprints;
}
@Override
public Map<OpenPgpV4Fingerprint, Date> getAvailableKeysFingerprints(BareJid contact) throws SmackOpenPgpException {
Map<OpenPgpV4Fingerprint, Date> availableFingerprints = new HashMap<>();
try {
PGPPublicKeyRingCollection publicKeys = getPublicKeyRings(contact);
Set<OpenPgpV4Fingerprint> fingerprints = new HashSet<>();
for (PGPPublicKeyRing ring : publicKeys != null ? publicKeys : Collections.<PGPPublicKeyRing>emptySet()) {
OpenPgpV4Fingerprint fingerprint = PainlessOpenPgpProvider.getFingerprint(ring.getPublicKey());
fingerprints.add(fingerprint);
}
Map<OpenPgpV4Fingerprint, Date> announced = getAnnouncedKeysFingerprints(contact);
for (OpenPgpV4Fingerprint fingerprint : fingerprints) {
if (announced.containsKey(fingerprint)) {
availableFingerprints.put(fingerprint, announced.get(fingerprint));
}
}
} catch (PGPException | IOException e) {
throw new SmackOpenPgpException("Could not read public keys of contact " + contact.toString(), e);
}
return availableFingerprints;
}
@Override
public Map<OpenPgpV4Fingerprint, Date> getAnnouncedKeysFingerprints(BareJid contact) {
try {
File file = getContactsPubkeyAnnouncementFile(contact, false);
return loadFingerprintsAndDates(file);
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Could not read announced fingerprints of contact " + contact, e);
}
return new HashMap<>();
}
@Override
public void setAnnouncedKeysFingerprints(BareJid contact, Map<OpenPgpV4Fingerprint, Date> fingerprints) {
try {
File file = getContactsPubkeyAnnouncementFile(contact, true);
writeFingerprintsAndDates(fingerprints, file);
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Could not write announced fingerprints of " + contact.toString(), e);
}
}
@Override
public Map<OpenPgpV4Fingerprint, Date> getPubkeysLastRevisions(BareJid owner) {
try {
return loadFingerprintsAndDates(getContactsPubkeyRevisionInfoFile(owner, false));
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Could not read revision dates of pubkeys of " + owner.toString(), e);
return new HashMap<>();
}
}
private static Map<OpenPgpV4Fingerprint, Date> loadFingerprintsAndDates(File file) throws IOException {
Map<OpenPgpV4Fingerprint, Date> revisionDates = new HashMap<>();
BufferedReader reader = null;
try {
reader = Files.newBufferedReader(file.toPath(), Charset.forName("UTF-8"));
int lineNr = 0;
String line;
while ((line = reader.readLine()) != null) {
lineNr++;
line = line.trim();
if (line.isEmpty()) {
continue;
}
String[] split = line.split(" ");
if (split.length != 2) {
continue;
}
try {
OpenPgpV4Fingerprint fingerprint = new OpenPgpV4Fingerprint(split[0]);
Date date = XmppDateTime.parseXEP0082Date(split[1]);
revisionDates.put(fingerprint, date);
} catch (Exception e) {
LOGGER.log(Level.WARNING, "Encountered illegal line in file " +
file.getAbsolutePath() + ": " + lineNr, e);
}
}
reader.close();
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Could not read fingerprints and dates from file " + file.getAbsolutePath(), e);
if (reader != null) {
reader.close();
}
}
return revisionDates;
}
private static void writeFingerprintsAndDates(Map<OpenPgpV4Fingerprint, Date> fingerprints, File file) throws IOException {
BufferedWriter writer = null;
try {
writer = Files.newBufferedWriter(file.toPath(), Charset.forName("UTF-8"));
for (OpenPgpV4Fingerprint fingerprint : fingerprints.keySet()) {
Date date = fingerprints.get(fingerprint);
String line = fingerprint.toString() + " " + (date != null ? XmppDateTime.formatXEP0082Date(date) : XmppDateTime.formatXEP0082Date(new Date()));
writer.write(line);
}
writer.close();
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Could not write fingerprints and dates to file " + file.getAbsolutePath());
if (writer != null) {
writer.close();
}
}
}
@Override
public void setPubkeysLastRevision(BareJid owner, Map<OpenPgpV4Fingerprint, Date> revisionDates) {
try {
File file = getContactsPubkeyRevisionInfoFile(owner, true);
writeFingerprintsAndDates(revisionDates, file);
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Could not write last pubkey revisions of " + owner, e);
}
}
@Override
public MultiMap<BareJid, OpenPgpV4Fingerprint> getAllContactsTrustedFingerprints() {
MultiMap<BareJid, OpenPgpV4Fingerprint> trustedFingerprints = new MultiMap<>();
try {
File contactsDir = getContactsPath(false);
if (!contactsDir.exists()) {
LOGGER.log(Level.FINE, "Contacts directory does not exists yet.");
return trustedFingerprints;
}
File[] subDirectories = contactsDir.listFiles(directoryFilter);
if (subDirectories == null) {
return trustedFingerprints;
}
for (File contact : subDirectories) {
BareJid jid = JidCreate.bareFrom(contact.getName());
try {
PGPPublicKeyRingCollection publicKeyRings = getPublicKeyRings(jid);
for (PGPPublicKeyRing ring : publicKeyRings) {
OpenPgpV4Fingerprint fingerprint = PainlessOpenPgpProvider.getFingerprint(ring.getPublicKey());
trustedFingerprints.put(jid, fingerprint);
}
} catch (PGPException e) {
LOGGER.log(Level.WARNING, "Could not read public key ring of " + jid, e);
}
}
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Could not read contacts directory", e);
}
return trustedFingerprints;
}
@Override
public byte[] getPublicKeyRingBytes(BareJid owner, OpenPgpV4Fingerprint fingerprint)
throws MissingOpenPgpPublicKeyException {
try {
PGPPublicKeyRingCollection publicKeyRings = getPublicKeyRings(owner);
PGPPublicKeyRing ring = publicKeyRings.getPublicKeyRing(fingerprint.getKeyId());
if (ring != null) {
return ring.getEncoded(true);
} else {
throw new MissingOpenPgpPublicKeyException(owner, fingerprint);
}
} catch (IOException | PGPException e) {
throw new MissingOpenPgpPublicKeyException(owner, fingerprint, e);
}
}
@Override
public byte[] getSecretKeyRingBytes(BareJid owner, OpenPgpV4Fingerprint fingerprint)
throws MissingOpenPgpKeyPairException {
try {
PGPSecretKeyRingCollection secretKeyRings = getSecretKeyRings(owner);
PGPSecretKeyRing ring = secretKeyRings.getSecretKeyRing(fingerprint.getKeyId());
if (ring != null) {
return ring.getEncoded();
} else {
throw new MissingOpenPgpKeyPairException(owner, fingerprint);
}
} catch (IOException | PGPException e) {
throw new MissingOpenPgpKeyPairException(owner, fingerprint, e);
}
}
/*
####################################################################################################################
File System Hierarchy
####################################################################################################################
*/
private File getStorePath(boolean create)
throws IOException {
if (create && !basePath.exists()) {
createDirectoryOrThrow(basePath);
}
return basePath;
}
private File getContactsPath(boolean create) throws IOException {
File path = new File(getStorePath(create), "contacts");
if (create && !path.exists()) {
createDirectoryOrThrow(path);
}
return path;
}
private File getContactsPath(BareJid owner, boolean create) throws IOException {
File path = new File(getContactsPath(create), owner.toString());
if (create && !path.exists()) {
createDirectoryOrThrow(path);
}
return path;
}
private File getContactsPubringFile(BareJid owner, boolean create) throws IOException {
File file = new File(getContactsPath(owner, create), "pubring.pkr");
if (create && !file.exists()) {
createFileOrThrow(file);
}
return file;
}
private File getContactsSecringFile(BareJid owner, boolean create) throws IOException {
File file = new File(getContactsPath(owner, create), "secring.skr");
if (create && !file.exists()) {
createFileOrThrow(file);
}
return file;
}
private File getContactsPubkeyRevisionInfoFile(BareJid owner, boolean create) throws IOException {
File file = new File(getContactsPath(owner, create), "revisionDates.lst");
if (create && !file.exists()) {
createFileOrThrow(file);
}
return file;
}
private File getContactsPubkeyAnnouncementFile(BareJid owner, boolean create) throws IOException {
File file = new File(getContactsPath(owner, create), "announcedKeys.lst");
if (create && !file.exists()) {
createFileOrThrow(file);
}
return file;
}
private static void createDirectoryOrThrow(File dir) throws IOException {
if (!dir.mkdirs()) {
throw new IOException("Could not create directory \"" + dir.getAbsolutePath() + "\"");
}
}
private static void createFileOrThrow(File file) throws IOException {
if (!file.createNewFile()) {
throw new IOException("Could not create file \"" + file.getAbsolutePath() + "\"");
}
}
private static final FileFilter directoryFilter = new FileFilter() {
@Override
public boolean accept(File file) {
return file != null && file.isDirectory();
}
};
}

View file

@ -1,452 +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.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jivesoftware.smack.util.MultiMap;
import org.jivesoftware.smackx.ox.OpenPgpProvider;
import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint;
import org.jivesoftware.smackx.ox.callback.backup.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.selection_strategy.BareJidUserId;
import org.jivesoftware.smackx.ox.util.DecryptedBytesAndMetadata;
import org.jivesoftware.smackx.ox.util.KeyBytesAndFingerprint;
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.openpgp.PGPSecretKeyRingCollection;
import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
import org.bouncycastle.util.encoders.Hex;
import org.bouncycastle.util.io.Streams;
import org.jxmpp.jid.BareJid;
import org.pgpainless.pgpainless.PGPainless;
import org.pgpainless.pgpainless.algorithm.SymmetricKeyAlgorithm;
import org.pgpainless.pgpainless.decryption_verification.DecryptionStream;
import org.pgpainless.pgpainless.decryption_verification.PainlessResult;
import org.pgpainless.pgpainless.key.generation.type.length.RsaLength;
import org.pgpainless.pgpainless.key.protection.SecretKeyRingProtector;
import org.pgpainless.pgpainless.util.BCUtil;
public class PainlessOpenPgpProvider implements OpenPgpProvider {
private static final Logger LOGGER = Logger.getLogger(PainlessOpenPgpProvider.class.getName());
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 {
PGPSecretKeyRing secretKeyRing;
SecretKeyRingProtector protector = getStore().getSecretKeyProtector();
try {
secretKeyRing = getStore()
.getSecretKeyRings(owner)
.getSecretKeyRing(
signingKey.getKeyId());
} catch (PGPException e) {
LOGGER.log(Level.INFO, "Could not get secret key with id " + Long.toHexString(signingKey.getKeyId()), e);
throw new MissingOpenPgpKeyPairException(owner, signingKey, e);
}
MultiMap<BareJid, PGPPublicKeyRing> publicKeyRingMultiMap = new MultiMap<>();
for (BareJid jid : encryptionKeys.keySet()) {
try {
PGPPublicKeyRingCollection publicKeyRings = getStore().getPublicKeyRings(jid);
for (OpenPgpV4Fingerprint f : encryptionKeys.getAll(jid)) {
PGPPublicKeyRing ring = publicKeyRings.getPublicKeyRing(f.getKeyId());
if (ring != null) {
publicKeyRingMultiMap.put(jid, ring);
}
}
} catch (PGPException e) {
LOGGER.log(Level.INFO, "Could get public keys of " + jid.toString());
throw new MissingOpenPgpPublicKeyException(owner, encryptionKeys.getFirst(jid));
}
}
return signAndEncryptImpl(element, secretKeyRing, protector, publicKeyRingMultiMap);
}
byte[] signAndEncryptImpl(SigncryptElement element,
PGPSecretKeyRing signingKey,
SecretKeyRingProtector secretKeyRingProtector,
MultiMap<BareJid, PGPPublicKeyRing> encryptionKeys)
throws SmackOpenPgpException, IOException {
InputStream fromPlain = element.toInputStream();
ByteArrayOutputStream encryptedBytes = new ByteArrayOutputStream();
OutputStream toEncrypted;
try {
toEncrypted = PGPainless.createEncryptor()
.onOutputStream(encryptedBytes)
.toRecipients(new ArrayList<>(encryptionKeys.values()).toArray(new PGPPublicKeyRing[] {}))
.usingSecureAlgorithms()
.signWith(secretKeyRingProtector, signingKey)
.noArmor();
} catch (PGPException | IOException e) {
throw new SmackOpenPgpException(e);
}
Streams.pipeAll(fromPlain, toEncrypted);
toEncrypted.flush();
toEncrypted.close();
encryptedBytes.close();
return encryptedBytes.toByteArray();
}
@Override
public byte[] sign(SignElement element, OpenPgpV4Fingerprint signingKeyFingerprint)
throws MissingOpenPgpKeyPairException, IOException, SmackOpenPgpException {
PGPSecretKeyRing signingKeyRing;
try {
signingKeyRing = store.getSecretKeyRings(owner).getSecretKeyRing(signingKeyFingerprint.getKeyId());
} catch (PGPException e) {
throw new MissingOpenPgpKeyPairException(owner, signingKeyFingerprint, e);
}
return signImpl(element, signingKeyRing, store.getSecretKeyProtector());
}
byte[] signImpl(SignElement element, PGPSecretKeyRing signingKey, SecretKeyRingProtector secretKeyRingProtector)
throws IOException, SmackOpenPgpException {
ByteArrayOutputStream toSigned = new ByteArrayOutputStream();
OutputStream signer;
try {
signer = PGPainless.createEncryptor().onOutputStream(toSigned)
.doNotEncrypt()
.signWith(secretKeyRingProtector, signingKey)
.noArmor();
} catch (PGPException e) {
throw new SmackOpenPgpException(e);
}
InputStream fromPlain = element.toInputStream();
Streams.pipeAll(fromPlain, signer);
fromPlain.close();
signer.close();
return toSigned.toByteArray();
}
@Override
public byte[] encrypt(CryptElement element, MultiMap<BareJid, OpenPgpV4Fingerprint> encryptionKeyFingerprints)
throws IOException, SmackOpenPgpException {
PGPPublicKeyRing[] allRecipientsKeys = getEncryptionKeys(encryptionKeyFingerprints);
return encryptImpl(element, allRecipientsKeys);
}
byte[] encryptImpl(CryptElement element, PGPPublicKeyRing[] encryptionKeys)
throws IOException, SmackOpenPgpException {
InputStream fromPlain = element.toInputStream();
ByteArrayOutputStream encrypted = new ByteArrayOutputStream();
OutputStream encryptor;
try {
encryptor = PGPainless.createEncryptor()
.onOutputStream(encrypted)
.toRecipients(encryptionKeys)
.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 {
PGPSecretKeyRingCollection secretKeyRings;
try {
secretKeyRings = getStore().getSecretKeyRings(owner);
} catch (PGPException | IOException e) {
LOGGER.log(Level.INFO, "Could not get secret keys of user " + owner);
throw new MissingOpenPgpKeyPairException(owner, getStore().getSigningKeyPairFingerprint());
}
SecretKeyRingProtector protector = getStore().getSecretKeyProtector();
List<OpenPgpV4Fingerprint> trustedFingerprints = getStore().getAllContactsTrustedFingerprints().getAll(sender);
Set<Long> trustedKeyIds = new HashSet<>();
for (OpenPgpV4Fingerprint fingerprint : trustedFingerprints) {
trustedKeyIds.add(fingerprint.getKeyId());
}
PGPPublicKeyRingCollection publicKeyRings;
try {
publicKeyRings = getStore().getPublicKeyRings(sender);
} catch (PGPException | IOException e) {
LOGGER.log(Level.INFO, "Could not get public keys of sender " + sender.toString(), e);
if (missingPublicKeyCallback != null) {
// TODO: Handle missing key
}
throw new SmackOpenPgpException(e);
}
Iterator<PGPPublicKeyRing> iterator = publicKeyRings.getKeyRings();
Set<PGPPublicKeyRing> trustedKeys = new HashSet<>();
while (iterator.hasNext()) {
PGPPublicKeyRing ring = iterator.next();
if (trustedKeyIds.contains(ring.getPublicKey().getKeyID())) {
trustedKeys.add(ring);
}
}
try {
return decryptImpl(bytes, secretKeyRings, protector, trustedKeys);
} catch (IOException e) {
throw new SmackOpenPgpException(e);
}
}
DecryptedBytesAndMetadata decryptImpl(byte[] bytes, PGPSecretKeyRingCollection decryptionKeys,
SecretKeyRingProtector protector,
Set<PGPPublicKeyRing> verificationKeys)
throws SmackOpenPgpException, IOException {
InputStream encryptedBytes = new ByteArrayInputStream(bytes);
ByteArrayOutputStream toPlain = new ByteArrayOutputStream();
DecryptionStream fromEncrypted;
try {
fromEncrypted = PGPainless.createDecryptor()
.onInputStream(encryptedBytes)
.decryptWith(protector, decryptionKeys)
.verifyWith(verificationKeys)
.ignoreMissingPublicKeys()
.build();
} catch (IOException | PGPException e) {
throw new SmackOpenPgpException(e);
}
Streams.pipeAll(fromEncrypted, toPlain);
fromEncrypted.close();
toPlain.flush();
toPlain.close();
PainlessResult result = fromEncrypted.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(), SymmetricKeyAlgorithm.AES_256);
} 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 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);
}
}
PGPPublicKeyRing[] allEncryptionKeys = new PGPPublicKeyRing[allRecipientsKeys.size()];
Iterator<PGPPublicKeyRing> iterator = allRecipientsKeys.iterator();
for (int i = 0; i < allEncryptionKeys.length; i++) {
allEncryptionKeys[i] = iterator.next();
}
return allEncryptionKeys;
}
private PGPSecretKeyRing getSigningKey(OpenPgpV4Fingerprint signingKey)
throws IOException, MissingOpenPgpKeyPairException {
PGPSecretKeyRing signingKeyRing;
try {
signingKeyRing = store.getSecretKeyRings(owner).getSecretKeyRing(signingKey.getKeyId());
} catch (PGPException e) {
throw new MissingOpenPgpKeyPairException(owner, signingKey, 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, IOException, SmackOpenPgpException {
PGPPublicKeyRing publicKeys = new PGPPublicKeyRing(bytes, new BcKeyFingerprintCalculator());
// LOGGER.log(Level.INFO, "Importing key " + Long.toHexString(publicKeys.getPublicKey().getKeyID()));
return importPublicKey(owner, publicKeys);
}
public OpenPgpV4Fingerprint importPublicKey(BareJid owner, PGPPublicKeyRing ring)
throws SmackOpenPgpException, IOException, MissingUserIdOnKeyException {
if (!new BareJidUserId.PubRingSelectionStrategy().accept(owner, ring)) {
throw new MissingUserIdOnKeyException(owner, ring.getPublicKey().getKeyID());
}
try {
PGPPublicKeyRingCollection publicKeyRings = getStore().getPublicKeyRings(owner);
if (publicKeyRings == null) {
publicKeyRings = new PGPPublicKeyRingCollection(Collections.singleton(ring));
} else {
try {
publicKeyRings = PGPPublicKeyRingCollection.addPublicKeyRing(publicKeyRings, ring);
} catch (IllegalArgumentException e) {
LOGGER.log(Level.INFO, "Skip key " + Long.toHexString(ring.getPublicKey().getKeyID()) +
" as it is already in the public key ring.");
}
}
getStore().storePublicKeyRing(owner, publicKeyRings);
} catch (PGPException e) {
throw new SmackOpenPgpException(e);
}
return getFingerprint(ring.getPublicKey());
}
@Override
public OpenPgpV4Fingerprint importSecretKey(BareJid owner, byte[] bytes)
throws MissingUserIdOnKeyException, SmackOpenPgpException, IOException {
PGPSecretKeyRing importSecretKeys;
try {
importSecretKeys = new PGPSecretKeyRing(bytes, new BcKeyFingerprintCalculator());
} catch (PGPException | IOException e) {
throw new SmackOpenPgpException("Could not deserialize PGP secret key of " + owner.toString(), e);
}
if (!new BareJidUserId.SecRingSelectionStrategy().accept(owner, importSecretKeys)) {
throw new MissingUserIdOnKeyException(owner, importSecretKeys.getPublicKey().getKeyID());
}
PGPSecretKeyRingCollection secretKeyRings;
try {
secretKeyRings = getStore().getSecretKeyRings(owner);
} catch (PGPException | IOException e) {
throw new SmackOpenPgpException("Could not load secret key ring of " + owner.toString(), e);
}
if (secretKeyRings == null) {
try {
secretKeyRings = new PGPSecretKeyRingCollection(Collections.singleton(importSecretKeys));
} catch (IOException | PGPException e) {
throw new SmackOpenPgpException("Could not create SecretKeyRingCollection from SecretKeyRing.", e);
}
} else {
try {
secretKeyRings = PGPSecretKeyRingCollection.addSecretKeyRing(secretKeyRings, importSecretKeys);
} catch (IllegalArgumentException e) {
LOGGER.log(Level.INFO, "Skip key " + Long.toHexString(importSecretKeys.getPublicKey().getKeyID()) +
" as it is already part of the key ring.");
}
}
getStore().storeSecretKeyRing(owner, secretKeyRings);
PGPPublicKeyRing publicKeys = BCUtil.publicKeyRingFromSecretKeyRing(importSecretKeys);
importPublicKey(owner, publicKeys);
return getFingerprint(publicKeys.getPublicKey());
}
@Override
public OpenPgpV4Fingerprint importSecretKey(byte[] bytes)
throws MissingUserIdOnKeyException, SmackOpenPgpException, IOException {
return importSecretKey(owner, bytes);
}
public static OpenPgpV4Fingerprint getFingerprint(PGPPublicKey publicKey) {
byte[] hex = Hex.encode(publicKey.getFingerprint());
return new OpenPgpV4Fingerprint(hex);
}
}

View file

@ -1,48 +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.IOException;
import org.jivesoftware.smackx.ox.OpenPgpStore;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
import org.jxmpp.jid.BareJid;
import org.pgpainless.pgpainless.key.protection.SecretKeyRingProtector;
public interface PainlessOpenPgpStore extends OpenPgpStore {
PGPPublicKeyRingCollection getPublicKeyRings(BareJid owner) throws IOException, PGPException;
PGPSecretKeyRingCollection getSecretKeyRings(BareJid owner) throws IOException, PGPException;
void storePublicKeyRing(BareJid owner, PGPPublicKeyRingCollection publicKeys) throws IOException;
void storeSecretKeyRing(BareJid owner, PGPSecretKeyRingCollection secretKeys) throws IOException;
byte[] loadPublicKeyRingBytes(BareJid owner);
byte[] loadSecretKeyRingBytes(BareJid owner);
void storePublicKeyRingBytes(BareJid owner, byte[] bytes);
void storeSecretKeyRingBytes(BareJid owner, byte[] bytes);
SecretKeyRingProtector getSecretKeyProtector();
}

View file

@ -1,86 +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 static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertTrue;
import java.io.File;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.util.Arrays;
import java.util.Collections;
import org.jivesoftware.smack.util.FileUtils;
import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint;
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.SecretKeyBackupHelper;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.jxmpp.jid.BareJid;
import org.jxmpp.jid.JidTestUtil;
import org.pgpainless.pgpainless.key.protection.UnprotectedKeysProtector;
public class BackupRestoreTest extends OxTestSuite {
private static final File backupPath = FileUtils.getTempDir("ox-backup");
private static final File restorePath = FileUtils.getTempDir("ox-restore");
private static final BareJid owner = JidTestUtil.BARE_JID_1;
@BeforeClass
@AfterClass
public static void deletePaths() {
FileUtils.deleteDirectory(backupPath);
FileUtils.deleteDirectory(restorePath);
}
@Test
public void test()
throws NoSuchAlgorithmException, IOException, InvalidAlgorithmParameterException, NoSuchProviderException,
SmackOpenPgpException, MissingUserIdOnKeyException, InvalidBackupCodeException,
MissingOpenPgpKeyPairException, MissingOpenPgpPublicKeyException {
FileBasedPainlessOpenPgpStore backupStore = new FileBasedPainlessOpenPgpStore(backupPath, new UnprotectedKeysProtector());
FileBasedPainlessOpenPgpStore restoreStore = new FileBasedPainlessOpenPgpStore(restorePath, new UnprotectedKeysProtector());
PainlessOpenPgpProvider backupProvider = new PainlessOpenPgpProvider(owner, backupStore);
PainlessOpenPgpProvider restoreProvider = new PainlessOpenPgpProvider(owner, restoreStore);
KeyBytesAndFingerprint key = backupProvider.generateOpenPgpKeyPair(owner);
backupProvider.importSecretKey(key.getBytes());
final String backupCode = SecretKeyBackupHelper.generateBackupPassword();
SecretkeyElement backup = SecretKeyBackupHelper.createSecretkeyElement(backupProvider, owner, Collections.singleton(key.getFingerprint()), backupCode);
OpenPgpV4Fingerprint fingerprint = SecretKeyBackupHelper.restoreSecretKeyBackup(restoreProvider, backup, backupCode);
assertEquals(key.getFingerprint(), fingerprint);
assertTrue(Arrays.equals(backupStore.getSecretKeyRingBytes(owner, fingerprint), restoreStore.getSecretKeyRingBytes(owner, fingerprint)));
assertTrue(Arrays.equals(backupStore.getPublicKeyRingBytes(owner, fingerprint), restoreStore.getPublicKeyRingBytes(owner, fingerprint)));
}
}

View file

@ -1,138 +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 static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertNotNull;
import static junit.framework.TestCase.assertTrue;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jivesoftware.smack.DummyConnection;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.util.FileUtils;
import org.jivesoftware.smackx.ox.OpenPgpContact;
import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint;
import org.jivesoftware.smackx.ox.TestKeys;
import org.jivesoftware.smackx.ox.element.OpenPgpContentElement;
import org.jivesoftware.smackx.ox.element.OpenPgpElement;
import org.jivesoftware.smackx.ox.element.PubkeyElement;
import org.jivesoftware.smackx.ox.element.SigncryptElement;
import org.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.bouncycastle.util.encoders.Base64;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.jxmpp.jid.BareJid;
import org.pgpainless.pgpainless.key.protection.UnprotectedKeysProtector;
import org.pgpainless.pgpainless.util.BCUtil;
import org.xmlpull.v1.XmlPullParserException;
public class DryOxEncryptionTest extends OxTestSuite {
private static final Logger LOGGER = Logger.getLogger(DryOxEncryptionTest.class.getName());
private static final Charset UTF8 = Charset.forName("UTF-8");
private static final File julietPath = FileUtils.getTempDir("ox-juliet");
private static final File romeoPath = FileUtils.getTempDir("ox-romeo");
@BeforeClass
@AfterClass
public static void deletePath() {
LOGGER.log(Level.INFO, "Delete paths " + julietPath.getAbsolutePath() + " " + romeoPath.getAbsolutePath());
FileUtils.deleteDirectory(julietPath);
FileUtils.deleteDirectory(romeoPath);
}
// @Test
public void dryEncryptionTest()
throws IOException, SmackOpenPgpException, MissingUserIdOnKeyException, MissingOpenPgpPublicKeyException,
MissingOpenPgpKeyPairException, XmlPullParserException, SmackException.NotLoggedInException {
BareJid julietJid = TestKeys.JULIET_JID;
BareJid romeoJid = TestKeys.ROMEO_JID;
XMPPConnection julietCon = new DummyConnection();
XMPPConnection romeoCon = new DummyConnection();
FileBasedPainlessOpenPgpStore julietStore = new FileBasedPainlessOpenPgpStore(julietPath, new UnprotectedKeysProtector());
FileBasedPainlessOpenPgpStore romeoStore = new FileBasedPainlessOpenPgpStore(romeoPath, new UnprotectedKeysProtector());
PainlessOpenPgpProvider julietProvider = new PainlessOpenPgpProvider(julietJid, julietStore);
PainlessOpenPgpProvider romeoProvider = new PainlessOpenPgpProvider(romeoJid, romeoStore);
OpenPgpV4Fingerprint julietFinger = julietProvider.importSecretKey(julietJid,
BCUtil.getDecodedBytes(TestKeys.JULIET_PRIV.getBytes(UTF8)));
OpenPgpV4Fingerprint romeoFinger = romeoProvider.importSecretKey(romeoJid,
BCUtil.getDecodedBytes(TestKeys.ROMEO_PRIV.getBytes(UTF8)));
julietStore.setSigningKeyPairFingerprint(julietFinger);
romeoStore.setSigningKeyPairFingerprint(romeoFinger);
byte[] julietPubBytes = julietStore.getPublicKeyRingBytes(julietJid, julietFinger);
byte[] romeoPubBytes = romeoStore.getPublicKeyRingBytes(romeoJid, romeoFinger);
assertNotNull(julietPubBytes);
assertNotNull(romeoPubBytes);
assertTrue(julietPubBytes.length != 0);
assertTrue(romeoPubBytes.length != 0);
PubkeyElement julietPub = new PubkeyElement(new PubkeyElement.PubkeyDataElement(
Base64.encode(julietStore.getPublicKeyRingBytes(julietJid, julietFinger))),
new Date());
PubkeyElement romeoPub = new PubkeyElement(new PubkeyElement.PubkeyDataElement(
Base64.encode(romeoStore.getPublicKeyRingBytes(romeoJid, romeoFinger))),
new Date());
julietProvider.importPublicKey(romeoJid, Base64.decode(romeoPub.getDataElement().getB64Data()));
romeoProvider.importPublicKey(julietJid, Base64.decode(julietPub.getDataElement().getB64Data()));
julietStore.setAnnouncedKeysFingerprints(romeoJid, Collections.singletonMap(romeoFinger, new Date()));
romeoStore.setAnnouncedKeysFingerprints(julietJid, Collections.singletonMap(julietFinger, new Date()));
OpenPgpContact julietForRomeo = new OpenPgpContact(romeoProvider, julietJid, romeoCon);
OpenPgpContact romeoForJuliet = new OpenPgpContact(julietProvider, romeoJid, julietCon);
String bodyText = "Finden wir eine Kompromisslösung machen wir es so, wie ich es sage.";
List<ExtensionElement> payload = Collections.<ExtensionElement>singletonList(new Message.Body("de",
bodyText));
OpenPgpElement encrypted = romeoForJuliet.encryptAndSign(payload);
LOGGER.log(Level.INFO, encrypted.toXML(null).toString());
OpenPgpContentElement decrypted = julietForRomeo.receive(encrypted);
assertTrue(decrypted instanceof SigncryptElement);
assertEquals(1, decrypted.getExtensions().size());
Message.Body body = (Message.Body) decrypted.getExtensions().get(0);
assertEquals(bodyText, body.getMessage());
}
}

View file

@ -1,103 +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 static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertTrue;
import java.io.File;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.util.Arrays;
import java.util.Collections;
import org.jivesoftware.smack.util.FileUtils;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.jxmpp.jid.BareJid;
import org.jxmpp.jid.JidTestUtil;
import org.pgpainless.pgpainless.PGPainless;
import org.pgpainless.pgpainless.key.generation.type.length.RsaLength;
import org.pgpainless.pgpainless.key.protection.UnprotectedKeysProtector;
import org.pgpainless.pgpainless.util.BCUtil;
public class FileBasedPainlessOpenPgpStoreTest extends OxTestSuite {
private final File basePath;
private final BareJid alice;
private final BareJid bob;
private FileBasedPainlessOpenPgpStore store;
public FileBasedPainlessOpenPgpStoreTest() {
super();
this.basePath = FileUtils.getTempDir("ox-filebased-store-test");
this.alice = JidTestUtil.BARE_JID_1;
this.bob = JidTestUtil.BARE_JID_2;
}
@After
@Before
public void deleteStore() {
FileUtils.deleteDirectory(basePath);
this.store = new FileBasedPainlessOpenPgpStore(basePath, new UnprotectedKeysProtector());
}
@Test
public void storeSecretKeyRingsTest()
throws PGPException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException,
IOException {
PGPSecretKeyRing secretKey = PGPainless.generateKeyRing().simpleRsaKeyRing("xmpp:" + alice.toString(), RsaLength._3072);
PGPSecretKeyRingCollection saving = new PGPSecretKeyRingCollection(Collections.singleton(secretKey));
store.storeSecretKeyRing(alice, saving);
FileBasedPainlessOpenPgpStore store2 = new FileBasedPainlessOpenPgpStore(basePath, new UnprotectedKeysProtector());
PGPSecretKeyRingCollection restored = store2.getSecretKeyRings(alice);
assertTrue(Arrays.equals(saving.getEncoded(), restored.getEncoded()));
}
@Test
public void storePublicKeyRingTest()
throws PGPException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException,
IOException {
PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing().simpleRsaKeyRing("xmpp:" + alice.toString(), RsaLength._3072);
PGPPublicKeyRing publicKeys = BCUtil.publicKeyRingFromSecretKeyRing(secretKeys);
for (PGPSecretKey k : secretKeys) {
assertEquals(publicKeys.getPublicKey(k.getKeyID()), k.getPublicKey());
}
PGPPublicKeyRingCollection saving = new PGPPublicKeyRingCollection(Collections.singleton(publicKeys));
store.storePublicKeyRing(alice, saving);
FileBasedPainlessOpenPgpStore store2 = new FileBasedPainlessOpenPgpStore(basePath, new UnprotectedKeysProtector());
PGPPublicKeyRingCollection restored = store2.getPublicKeyRings(alice);
assertTrue(Arrays.equals(saving.getEncoded(), restored.getEncoded()));
}
}

View file

@ -1,33 +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.security.Security;
import org.jivesoftware.smack.test.util.SmackTestSuite;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.BeforeClass;
public abstract class OxTestSuite extends SmackTestSuite {
@BeforeClass
public static void registerProvider() {
Security.removeProvider("BC");
Security.addProvider(new BouncyCastleProvider());
}
}

View file

@ -1,52 +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 static junit.framework.TestCase.assertTrue;
import java.io.IOException;
import java.util.Arrays;
import java.util.Random;
import org.bouncycastle.openpgp.PGPDataValidationException;
import org.bouncycastle.openpgp.PGPException;
import org.junit.Test;
import org.pgpainless.pgpainless.PGPainless;
import org.pgpainless.pgpainless.algorithm.SymmetricKeyAlgorithm;
public class SymmetricEncryptionTest extends OxTestSuite {
@Test
public void successfulEncryptionDecryption() throws IOException, PGPException {
byte[] data = new byte[5000];
new Random().nextBytes(data);
byte[] encrypted = PGPainless.encryptWithPassword(data, "Password".toCharArray(), SymmetricKeyAlgorithm.AES_256);
byte[] decrypted = PGPainless.decryptWithPassword(encrypted, "Password".toCharArray());
assertTrue(Arrays.equals(data, decrypted));
}
@Test(expected = PGPDataValidationException.class)
public void failingEncryptionDecryption() throws IOException, PGPException {
byte[] data = new byte[5000];
new Random().nextBytes(data);
byte[] encrypted = PGPainless.encryptWithPassword(data, "Password".toCharArray(), SymmetricKeyAlgorithm.AES_256);
byte[] decrypted = PGPainless.decryptWithPassword(encrypted, "Swordfish".toCharArray());
}
}

View file

@ -1,75 +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.File;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import org.jivesoftware.smack.util.FileUtils;
import org.jivesoftware.smackx.ox.exception.MissingUserIdOnKeyException;
import org.jivesoftware.smackx.ox.exception.SmackOpenPgpException;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.jxmpp.jid.BareJid;
import org.jxmpp.jid.JidTestUtil;
import org.pgpainless.pgpainless.PGPainless;
import org.pgpainless.pgpainless.key.protection.UnprotectedKeysProtector;
public class UserIdTest extends OxTestSuite {
private final File path;
public UserIdTest() {
this.path = FileUtils.getTempDir("ox-user-id-test");
}
@After
@Before
public void deleteDir() {
FileUtils.deleteDirectory(path);
}
@Test
public void requireUserIdOnImportTest()
throws PGPException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException,
IOException, SmackOpenPgpException, MissingUserIdOnKeyException {
BareJid owner = JidTestUtil.BARE_JID_1;
FileBasedPainlessOpenPgpStore store = new FileBasedPainlessOpenPgpStore(path, new UnprotectedKeysProtector());
PainlessOpenPgpProvider provider = new PainlessOpenPgpProvider(owner, store);
PGPSecretKeyRing ownerKey = PGPainless.generateKeyRing().simpleEcKeyRing("xmpp:" + owner.toString());
provider.importSecretKey(owner, ownerKey.getEncoded());
}
@Test(expected = MissingUserIdOnKeyException.class)
public void throwOnMissingUserIdTest()
throws PGPException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException,
IOException, SmackOpenPgpException, MissingUserIdOnKeyException {
BareJid owner = JidTestUtil.BARE_JID_1;
BareJid stranger = JidTestUtil.BARE_JID_2;
FileBasedPainlessOpenPgpStore store = new FileBasedPainlessOpenPgpStore(path, new UnprotectedKeysProtector());
PainlessOpenPgpProvider provider = new PainlessOpenPgpProvider(owner, store);
PGPSecretKeyRing strangerKey = PGPainless.generateKeyRing().simpleEcKeyRing("xmpp:" + stranger.toString());
provider.importSecretKey(owner, strangerKey.getEncoded());
}
}

View file

@ -172,7 +172,7 @@ public final class OXInstantMessagingManager extends Manager implements Signcryp
StoreHint.set(message);
message.setBody("This message is encrypted using XEP-0374: OpenPGP for XMPP: Instant Messaging.");
contact.addSignedEncryptedPayloadTo(message, payload);
//contact.addSignedEncryptedPayloadTo(message, payload);
ChatManager.getInstanceFor(connection()).chatWith(contact.getJid().asEntityBareJidIfPossible()).send(message);
}

View file

@ -1,381 +1,51 @@
/**
*
* 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;
import java.io.IOException;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
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.element.OpenPgpContentElement;
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.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.PubSubDelegate;
import org.jivesoftware.smackx.pubsub.LeafNode;
import org.jivesoftware.smackx.pubsub.PubSubException;
import org.jivesoftware.smackx.ox.store.definition.OpenPgpStore;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.jxmpp.jid.BareJid;
import org.jxmpp.jid.Jid;
import org.xmlpull.v1.XmlPullParserException;
import org.pgpainless.pgpainless.key.OpenPgpV4Fingerprint;
public class OpenPgpContact {
private static final Logger LOGGER = Logger.getLogger(OpenPgpContact.class.getName());
private final Logger LOGGER;
private final BareJid jid;
protected final OpenPgpProvider cryptoProvider;
private final XMPPConnection connection;
protected final BareJid jid;
protected final OpenPgpStore store;
private Map<OpenPgpV4Fingerprint, Date> announcedKeys = null;
private Map<OpenPgpV4Fingerprint, Date> availableKeys = null;
private final Map<OpenPgpV4Fingerprint, Throwable> unfetchableKeys = new HashMap<>();
/**
* Create a OpenPgpContact.
*
* @param cryptoProvider {@link OpenPgpProvider}
* @param jid {@link BareJid} of the contact
* @param connection our authenticated {@link XMPPConnection}
*/
public OpenPgpContact(OpenPgpProvider cryptoProvider,
BareJid jid,
XMPPConnection connection) {
public OpenPgpContact(BareJid jid, OpenPgpStore store) {
this.jid = jid;
this.cryptoProvider = cryptoProvider;
this.connection = connection;
this.store = store;
LOGGER = Logger.getLogger(OpenPgpContact.class.getName() + ":" + jid.toString());
}
/**
* Return the {@link BareJid} of the contact.
*
* @return jid
*/
public BareJid getJid() {
return jid;
}
/**
* Return a {@link Map} of the announced keys of the contact and their last update dates.
*
* @return announced keys
*/
public Map<OpenPgpV4Fingerprint, Date> getAnnouncedKeys() {
if (announcedKeys == null) {
announcedKeys = cryptoProvider.getStore().getAnnouncedKeysFingerprints(getJid());
}
return announcedKeys;
public PGPPublicKeyRingCollection getAnyPublicKeys() throws IOException, PGPException {
return store.getPublicKeysOf(jid);
}
/**
* Return a {@link Map} of all locally available keys of the contact.
*
* Note: This list might contain keys which are no longer associated to the contact.
* For encryption please use {@link #getActiveKeys()} instead.
*
* @return available keys
* @throws SmackOpenPgpException if we cannot read the locally available keys for some reason
*/
public Map<OpenPgpV4Fingerprint, Date> getAvailableKeys() throws SmackOpenPgpException {
if (availableKeys == null) {
availableKeys = cryptoProvider.getStore().getAvailableKeysFingerprints(getJid());
}
return availableKeys;
}
public PGPPublicKeyRingCollection getAnnouncedPublicKeys() throws IOException, PGPException {
PGPPublicKeyRingCollection anyKeys = getAnyPublicKeys();
Set<OpenPgpV4Fingerprint> announced = store.getAnnouncedFingerprintsOf(jid).keySet();
/**
* Return a {@link Map} of all the keys which cannot be fetched and the reason why this is.
*
* @return unfetched keys
*/
public Map<OpenPgpV4Fingerprint, Throwable> getUnfetchableKeys() {
return unfetchableKeys;
}
PGPPublicKeyRingCollection announcedKeysCollection = anyKeys;
for (PGPPublicKeyRing ring : anyKeys) {
/**
* Return a {@link Set} of all active keys of this contact.
* Active keys are keys which are announced, not revoked or otherwise invalidated and locally available.
*
* @return active keys.
* @throws SmackOpenPgpException if we cannot access the keys of the contact.
*/
public Set<OpenPgpV4Fingerprint> getActiveKeys() throws SmackOpenPgpException {
Set<OpenPgpV4Fingerprint> fingerprints = getAvailableKeys().keySet();
fingerprints.retainAll(getAnnouncedKeys().keySet());
return fingerprints;
}
OpenPgpV4Fingerprint fingerprint = new OpenPgpV4Fingerprint(ring.getPublicKey());
/**
* Fetch the metadata node to get a {@link PublicKeysListElement} and update any missing or outdated keys.
*
* @throws InterruptedException if the thread is interrupted
* @throws XMPPException.XMPPErrorException in case of an XMPP protocol error
* @throws SmackOpenPgpException in case of an OpenPGP exception
* @throws PubSubException.NotAPubSubNodeException in case the metadata node is not a PubSub node
* @throws PubSubException.NotALeafNodeException in case the metadata node is not a {@link LeafNode}
* @throws SmackException.NotConnectedException in case we are not connected
* @throws SmackException.NoResponseException in case the server doesn't respond
*/
public void updateKeys()
throws InterruptedException, XMPPException.XMPPErrorException, SmackOpenPgpException,
PubSubException.NotAPubSubNodeException, PubSubException.NotALeafNodeException,
SmackException.NotConnectedException, SmackException.NoResponseException {
updateKeys(PubSubDelegate.fetchPubkeysList(connection, getJid()));
}
/**
* Update any missing or outdated keys based on the given {@link PublicKeysListElement}.
*
* @param metadata {@link PublicKeysListElement}
* @throws SmackOpenPgpException in case the available keys cannot be fetched
*/
public void updateKeys(PublicKeysListElement metadata)
throws SmackOpenPgpException {
storePublishedDevices(metadata);
this.availableKeys = getAvailableKeys();
for (OpenPgpV4Fingerprint fingerprint : announcedKeys.keySet()) {
Date announcedDate = announcedKeys.get(fingerprint);
Date availableDate = availableKeys.get(fingerprint);
if (availableDate == null || availableDate.before(announcedDate)) {
try {
updateKey(fingerprint);
unfetchableKeys.remove(fingerprint);
} catch (IOException | XMPPException.XMPPErrorException | SmackException | InterruptedException |
SmackOpenPgpException | MissingUserIdOnKeyException | NullPointerException e) {
LOGGER.log(Level.WARNING, "Could not update key " + fingerprint + " of " + getJid());
unfetchableKeys.put(fingerprint, e);
if (!announced.contains(fingerprint)) {
announcedKeysCollection = PGPPublicKeyRingCollection.removePublicKeyRing(announcedKeysCollection, ring);
}
}
}
}
/**
* Update the key identified by the {@code fingerprint}.
*
* @param fingerprint fingerprint of the key
* @throws InterruptedException if the thread is interrupted
* @throws XMPPException.XMPPErrorException in case of an XMPP protocol error
* @throws SmackException in case of an exception in Smack
* @throws IOException IO is dangerous
* @throws MissingUserIdOnKeyException if the key is missing a user id with the contacts jid
* @throws SmackOpenPgpException in case of an OpenPGP exception
*/
public void updateKey(OpenPgpV4Fingerprint fingerprint)
throws InterruptedException, XMPPException.XMPPErrorException, SmackException, IOException,
MissingUserIdOnKeyException, SmackOpenPgpException {
PubkeyElement pubkeyElement = PubSubDelegate.fetchPubkey(connection, getJid(), fingerprint);
if (pubkeyElement == null) {
throw new NullPointerException("Fetched pubkeyElement for key " + fingerprint + " of " + getJid() + " is null.");
}
byte[] base64 = pubkeyElement.getDataElement().getB64Data();
OpenPgpV4Fingerprint imported = importPublicKey(Base64.decode(base64));
if (!fingerprint.equals(imported)) {
// Not sure, if this can/should happen. Lets be safe and throw, even if its too late at this point.
throw new AssertionError("Fingerprint of imported key differs from expected fingerprint. " +
"Expected: " + fingerprint + " Imported: " + imported);
}
}
/**
* Import a public key.
*
* @param data OpenPgp keys byte representation.
* @return the fingerprint of the imported key.
* @throws SmackOpenPgpException in case of an OpenPGP error
* @throws MissingUserIdOnKeyException if the key is missing a user id with the contacts jid
* @throws IOException IO is dangerous
*/
private OpenPgpV4Fingerprint importPublicKey(byte[] data)
throws SmackOpenPgpException, MissingUserIdOnKeyException, IOException {
OpenPgpV4Fingerprint fingerprint = cryptoProvider.importPublicKey(getJid(), data);
availableKeys.put(fingerprint, new Date());
return fingerprint;
}
/**
* Process an incoming {@link PublicKeysListElement} and store the contained {@link OpenPgpV4Fingerprint}s and
* their publication dates in local storage.
*
* @param element publicKeysListElement
* @return {@link Map} with the contents of the element
*/
public Map<OpenPgpV4Fingerprint, Date> storePublishedDevices(PublicKeysListElement element) {
Map<OpenPgpV4Fingerprint, Date> announcedKeys = new HashMap<>();
for (OpenPgpV4Fingerprint f : element.getMetadata().keySet()) {
PublicKeysListElement.PubkeyMetadataElement meta = element.getMetadata().get(f);
announcedKeys.put(meta.getV4Fingerprint(), meta.getDate());
}
if (!announcedKeys.equals(this.announcedKeys)) {
cryptoProvider.getStore().setAnnouncedKeysFingerprints(getJid(), announcedKeys);
this.announcedKeys = announcedKeys;
}
return announcedKeys;
}
/**
* Get all keys to which a message sent to the contact would be encrypted to.
* Those are all active keys of the contact as well as our own active keys.
*
* @return encryption keys
* @throws SmackOpenPgpException if we cannot read the contacts keys or our own keys.
* @throws SmackException.NotLoggedInException if we are not logged in.
*/
private MultiMap<BareJid, OpenPgpV4Fingerprint> getEncryptionKeys()
throws SmackOpenPgpException, SmackException.NotLoggedInException {
OpenPgpSelf self = getSelf();
Set<OpenPgpV4Fingerprint> contactsKeys = getActiveKeys();
Set<OpenPgpV4Fingerprint> ourKeys = self.getActiveKeys();
MultiMap<BareJid, OpenPgpV4Fingerprint> recipientsKeys = new MultiMap<>();
for (OpenPgpV4Fingerprint fingerprint : contactsKeys) {
recipientsKeys.put(getJid(), fingerprint);
}
for (OpenPgpV4Fingerprint fingerprint : ourKeys) {
recipientsKeys.put(self.getJid(), fingerprint);
}
return recipientsKeys;
}
/**
* Get our OpenPgpSelf.
* The {@link OpenPgpSelf} contains all our fingerprints.
*
* @return openPgpSelf
* @throws SmackException.NotLoggedInException
*/
private OpenPgpSelf getSelf() throws SmackException.NotLoggedInException {
return OpenPgpManager.getInstanceFor(connection).getOpenPgpSelf();
}
/**
* Encrypt a message to a contact and ourselves and sign it with our signing key.
* The payload will be wrapped in a {@link SigncryptElement} before encryption.
*
* @param payload the payload we want to encrypt.
* @return {@link OpenPgpElement} containing the encrypted, signed {@link SigncryptElement}.
* @throws IOException IO is dangerous
* @throws SmackOpenPgpException OpenPgp is brittle
* @throws MissingOpenPgpKeyPairException if we cannot read our signing key pair
* @throws SmackException.NotLoggedInException if we are not logged in.
*/
public OpenPgpElement encryptAndSign(List<ExtensionElement> payload)
throws IOException, SmackOpenPgpException, MissingOpenPgpKeyPairException,
SmackException.NotLoggedInException {
OpenPgpSelf self = OpenPgpManager.getInstanceFor(connection).getOpenPgpSelf();
SigncryptElement preparedPayload = new SigncryptElement(
Collections.<Jid>singleton(getJid()),
payload);
byte[] encryptedBytes;
// Encrypt the payload
try {
encryptedBytes = cryptoProvider.signAndEncrypt(
preparedPayload,
self.getSigningKey(),
getEncryptionKeys());
} catch (MissingOpenPgpPublicKeyException e) {
throw new AssertionError("Missing OpenPGP public key, even though this should not happen here.", e);
}
return new OpenPgpElement(Base64.encodeToString(encryptedBytes));
}
/**
* Create a signed and encrypted {@link SigncryptElement} containing the {@code payload} and append it as a
* {@link OpenPgpElement} to the {@code message}.
*
* @param message {@link Message} which will transport the payload.
* @param payload payload that will be encrypted and signed.
* @throws IOException IO is dangerous.
* @throws SmackOpenPgpException OpenPGP is brittle.
* @throws MissingOpenPgpKeyPairException if we cannot access our signing key.
* @throws SmackException.NotLoggedInException if we are not logged in.
*/
public void addSignedEncryptedPayloadTo(Message message, List<ExtensionElement> payload)
throws IOException, SmackOpenPgpException, MissingOpenPgpKeyPairException,
SmackException.NotLoggedInException {
// Add encrypted payload to message
OpenPgpElement encryptedPayload = encryptAndSign(payload);
message.addExtension(encryptedPayload);
// Add additional information to the message
if (!ExplicitMessageEncryptionElement.hasProtocol(message,
ExplicitMessageEncryptionElement.ExplicitMessageEncryptionProtocol.openpgpV0)) {
message.addExtension(new ExplicitMessageEncryptionElement(
ExplicitMessageEncryptionElement.ExplicitMessageEncryptionProtocol.openpgpV0));
}
StoreHint.set(message);
message.setBody("This message is encrypted using XEP-0374: OpenPGP for XMPP: Instant Messaging.");
}
/**
* Process an incoming {@link OpenPgpElement} and return the decrypted and verified {@link OpenPgpContentElement}.
*
* @param element possibly encrypted, possibly signed {@link OpenPgpElement}.
* @return decrypted {@link OpenPgpContentElement}
* @throws XmlPullParserException if the decrypted message does not represent valid XML.
* @throws MissingOpenPgpKeyPairException if we are missing the public key counterpart of the key that signed the message.
* @throws SmackOpenPgpException if the message cannot be decrypted and or verified.
* @throws IOException IO is dangerous
*/
public OpenPgpContentElement receive(OpenPgpElement element)
throws XmlPullParserException, MissingOpenPgpKeyPairException, SmackOpenPgpException, IOException {
byte[] decoded = Base64.decode(element.getEncryptedBase64MessageContent());
DecryptedBytesAndMetadata decryptedBytes = cryptoProvider.decrypt(decoded, getJid(), null);
OpenPgpMessage openPgpMessage = new OpenPgpMessage(decryptedBytes.getBytes(),
new OpenPgpMessage.Metadata(decryptedBytes.getDecryptionKey(),
decryptedBytes.getVerifiedSignatures()));
return openPgpMessage.getOpenPgpContentElement();
return announcedKeysCollection;
}
}

View file

@ -18,7 +18,6 @@ package org.jivesoftware.smackx.ox;
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.publishPublicKey;
import java.io.IOException;
import java.io.InputStream;
@ -49,16 +48,14 @@ import org.jivesoftware.smackx.ox.callback.backup.AskForBackupCodeCallback;
import org.jivesoftware.smackx.ox.callback.backup.DisplayBackupCodeCallback;
import org.jivesoftware.smackx.ox.callback.backup.SecretKeyBackupSelectionCallback;
import org.jivesoftware.smackx.ox.callback.backup.SecretKeyRestoreSelectionCallback;
import org.jivesoftware.smackx.ox.crypto.OpenPgpProvider;
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.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.MissingUserIdOnKeyException;
import org.jivesoftware.smackx.ox.exception.NoBackupFoundException;
@ -66,11 +63,10 @@ import org.jivesoftware.smackx.ox.exception.SmackOpenPgpException;
import org.jivesoftware.smackx.ox.listener.internal.CryptElementReceivedListener;
import org.jivesoftware.smackx.ox.listener.internal.SignElementReceivedListener;
import org.jivesoftware.smackx.ox.listener.internal.SigncryptElementReceivedListener;
import org.jivesoftware.smackx.ox.util.KeyBytesAndFingerprint;
import org.jivesoftware.smackx.ox.store.definition.OpenPgpStore;
import org.jivesoftware.smackx.ox.util.PubSubDelegate;
import org.jivesoftware.smackx.ox.util.SecretKeyBackupHelper;
import org.jivesoftware.smackx.pep.PEPListener;
import org.jivesoftware.smackx.pep.PEPManager;
import org.jivesoftware.smackx.pubsub.EventElement;
import org.jivesoftware.smackx.pubsub.ItemsExtension;
import org.jivesoftware.smackx.pubsub.LeafNode;
@ -78,9 +74,11 @@ import org.jivesoftware.smackx.pubsub.PayloadItem;
import org.jivesoftware.smackx.pubsub.PubSubException;
import org.jivesoftware.smackx.pubsub.PubSubFeature;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.jxmpp.jid.BareJid;
import org.jxmpp.jid.EntityBareJid;
import org.xmlpull.v1.XmlPullParserException;
import org.pgpainless.pgpainless.key.OpenPgpV4Fingerprint;
public final class OpenPgpManager extends Manager {
@ -96,6 +94,8 @@ public final class OpenPgpManager extends Manager {
*/
private OpenPgpProvider provider;
private OpenPgpStore store;
private final Map<BareJid, OpenPgpContact> openPgpCapableContacts = new HashMap<>();
private final Set<SigncryptElementReceivedListener> signcryptElementReceivedListeners = new HashSet<>();
@ -129,6 +129,11 @@ public final class OpenPgpManager extends Manager {
return manager;
}
public BareJid getJidOrThrow() throws SmackException.NotLoggedInException {
throwIfNotAuthenticated();
return connection().getUser().asEntityBareJidOrThrow();
}
/**
* Set the {@link OpenPgpProvider} which will be used to process incoming OpenPGP elements,
* as well as to execute cryptographic operations.
@ -146,10 +151,8 @@ public final class OpenPgpManager extends Manager {
* @throws SmackException.NotLoggedInException if we are not logged in
*/
public OpenPgpSelf getOpenPgpSelf() throws SmackException.NotLoggedInException {
throwIfNotAuthenticated();
if (self == null) {
self = new OpenPgpSelf(provider, connection().getUser().asBareJid(), connection());
self = new OpenPgpSelf(getJidOrThrow(), store);
}
return self;
@ -182,25 +185,25 @@ public final class OpenPgpManager extends Manager {
BareJid ourJid = connection().getUser().asBareJid();
OpenPgpV4Fingerprint primaryFingerprint = getOurFingerprint();
// OpenPgpV4Fingerprint primaryFingerprint = getOurFingerprint();
if (primaryFingerprint == null) {
primaryFingerprint = generateAndImportKeyPair(ourJid);
}
// if (primaryFingerprint == null) {
// primaryFingerprint = generateAndImportKeyPair(ourJid);
// }
// Create <pubkey/> element
PubkeyElement pubkeyElement;
try {
pubkeyElement = createPubkeyElement(ourJid, primaryFingerprint, new Date());
} catch (MissingOpenPgpPublicKeyException e) {
throw new AssertionError("Cannot publish our public key, since it is missing (MUST NOT happen!)");
}
// try {
// pubkeyElement = createPubkeyElement(ourJid, primaryFingerprint, new Date());
// } catch (MissingOpenPgpPublicKeyException e) {
// throw new AssertionError("Cannot publish our public key, since it is missing (MUST NOT happen!)");
// }
// publish it
publishPublicKey(connection(), pubkeyElement, primaryFingerprint);
// publishPublicKey(connection(), pubkeyElement, primaryFingerprint);
// Subscribe to public key changes
PEPManager.getInstanceFor(connection()).addPEPListener(metadataListener);
// PEPManager.getInstanceFor(connection()).addPEPListener(metadataListener);
ServiceDiscoveryManager.getInstanceFor(connection())
.addFeature(PEP_NODE_PUBLIC_KEYS_NOTIFY);
}
@ -211,25 +214,23 @@ public final class OpenPgpManager extends Manager {
* @param ourJid our {@link BareJid}.
* @return {@link OpenPgpV4Fingerprint} of the generated key.
* @throws NoSuchAlgorithmException if the JVM doesn't support one of the used algorithms.
* @throws IOException IO is dangerous.
* @throws InvalidAlgorithmParameterException if the used algorithm parameters are invalid.
* @throws NoSuchProviderException if we are missing a cryptographic provider.
* @throws SmackOpenPgpException in case of an OpenPGP error.
*/
public OpenPgpV4Fingerprint generateAndImportKeyPair(BareJid ourJid)
throws NoSuchAlgorithmException, IOException, InvalidAlgorithmParameterException, NoSuchProviderException,
SmackOpenPgpException {
KeyBytesAndFingerprint bytesAndFingerprint = provider.generateOpenPgpKeyPair(ourJid);
OpenPgpV4Fingerprint fingerprint = bytesAndFingerprint.getFingerprint();
throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, NoSuchProviderException,
SmackOpenPgpException, PGPException {
PGPSecretKeyRing keyPair = store.generateKeyRing(ourJid);
// try {
// provider.importSecretKey(ourJid, keyPair);
// } catch (MissingUserIdOnKeyException e) {
// 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);
}
// throw new AssertionError(e);
// }
return fingerprint;
return new OpenPgpV4Fingerprint(keyPair);
}
/**
@ -237,9 +238,9 @@ public final class OpenPgpManager extends Manager {
*
* @return fingerprint.
*/
public OpenPgpV4Fingerprint getOurFingerprint() {
throwIfNoProviderSet();
return provider.getStore().getSigningKeyPairFingerprint();
public OpenPgpV4Fingerprint getOurFingerprint()
throws SmackException.NotLoggedInException, IOException, PGPException {
return getOpenPgpSelf().getSigningKeyFingerprint();
}
/**
@ -250,15 +251,7 @@ public final class OpenPgpManager extends Manager {
* @return {@link OpenPgpContact}.
*/
public OpenPgpContact getOpenPgpContact(EntityBareJid jid) {
OpenPgpContact openPgpContact = openPgpCapableContacts.get(jid);
if (openPgpContact == null) {
openPgpContact = new OpenPgpContact(provider, jid, connection());
openPgpCapableContacts.put(jid, openPgpContact);
}
return openPgpContact;
return store.getOpenPgpContact(jid);
}
@ -311,13 +304,13 @@ public final class OpenPgpManager extends Manager {
BareJid ownJid = connection().getUser().asBareJid();
String backupCode = SecretKeyBackupHelper.generateBackupPassword();
Set<OpenPgpV4Fingerprint> availableKeyPairs = provider.getStore().getAvailableKeyPairFingerprints(ownJid);
Set<OpenPgpV4Fingerprint> selectedKeyPairs = selectKeyCallback.selectKeysToBackup(availableKeyPairs);
// Set<OpenPgpV4Fingerprint> availableKeyPairs = provider.getStore().getAvailableKeyPairFingerprints(ownJid);
// Set<OpenPgpV4Fingerprint> selectedKeyPairs = selectKeyCallback.selectKeysToBackup(availableKeyPairs);
SecretkeyElement secretKey = SecretKeyBackupHelper.createSecretkeyElement(provider, ownJid, selectedKeyPairs, backupCode);
// SecretkeyElement secretKey = SecretKeyBackupHelper.createSecretkeyElement(provider, ownJid, selectedKeyPairs, backupCode);
PubSubDelegate.depositSecretKey(connection(), secretKey);
displayCodeCallback.displayBackupCode(backupCode);
// PubSubDelegate.depositSecretKey(connection(), secretKey);
// displayCodeCallback.displayBackupCode(backupCode);
}
/**
@ -401,19 +394,21 @@ public final class OpenPgpManager extends Manager {
};
private void processPublicKeysListElement(BareJid contact, PublicKeysListElement listElement) {
/*
OpenPgpContact openPgpContact = getOpenPgpContact(contact.asEntityBareJidIfPossible());
try {
openPgpContact.updateKeys(listElement);
} catch (SmackOpenPgpException e) {
LOGGER.log(Level.WARNING, "Could not read key ring of contact " + contact, e);
}
*/
}
private final IncomingChatMessageListener incomingOpenPgpMessageListener =
new IncomingChatMessageListener() {
@Override
public void newIncomingMessage(EntityBareJid from, Message message, Chat chat) {
/*
OpenPgpElement element = message.getExtension(OpenPgpElement.ELEMENT, OpenPgpElement.NAMESPACE);
if (element == null) {
// Message does not contain an OpenPgpElement -> discard
@ -457,6 +452,7 @@ public final class OpenPgpManager extends Manager {
else {
throw new AssertionError("Invalid element received: " + contentElement.getClass().getName());
}
*/
}
};
@ -477,7 +473,7 @@ public final class OpenPgpManager extends Manager {
private void processPublicKey(PubkeyElement pubkeyElement, BareJid owner)
throws MissingUserIdOnKeyException, IOException, SmackOpenPgpException {
byte[] base64 = pubkeyElement.getDataElement().getB64Data();
provider.importPublicKey(owner, Base64.decode(base64));
// provider.importPublicKey(owner, Base64.decode(base64));
}
/**
@ -495,8 +491,9 @@ public final class OpenPgpManager extends Manager {
OpenPgpV4Fingerprint fingerprint,
Date date)
throws MissingOpenPgpPublicKeyException {
byte[] keyBytes = provider.getStore().getPublicKeyRingBytes(owner, fingerprint);
return createPubkeyElement(keyBytes, date);
// byte[] keyBytes = provider.getStore().getPublicKeyRingBytes(owner, fingerprint);
// return createPubkeyElement(keyBytes, date);
return null;
}
/**

View file

@ -29,6 +29,7 @@ import org.jivesoftware.smackx.ox.element.SignElement;
import org.jivesoftware.smackx.ox.element.SigncryptElement;
import org.jivesoftware.smackx.ox.provider.OpenPgpContentElementProvider;
import org.pgpainless.pgpainless.key.OpenPgpV4Fingerprint;
import org.xmlpull.v1.XmlPullParserException;
/**
@ -133,31 +134,31 @@ public class OpenPgpMessage {
public static class Metadata {
private final Long encryptionKeyId;
private final Set<Long> validSignatureIds;
private final OpenPgpV4Fingerprint decryptionFingerprint;
private final Set<OpenPgpV4Fingerprint> validSignatureFingerprints;
public Metadata(Long encryptionKeyId, Set<Long> validSignatureIds) {
this.encryptionKeyId = encryptionKeyId;
this.validSignatureIds = validSignatureIds;
public Metadata(OpenPgpV4Fingerprint decryptionFingerprint, Set<OpenPgpV4Fingerprint> validSignatureFingerprints) {
this.decryptionFingerprint = decryptionFingerprint;
this.validSignatureFingerprints = validSignatureFingerprints;
}
public Long getEncryptionKeyId() {
return encryptionKeyId;
public OpenPgpV4Fingerprint getDecryptionFingerprint() {
return decryptionFingerprint;
}
public Set<Long> getValidSignatureIds() {
return new HashSet<>(validSignatureIds);
public Set<OpenPgpV4Fingerprint> getValidSignatureFingerprints() {
return new HashSet<>(validSignatureFingerprints);
}
public State getState() {
if (validSignatureIds.size() != 0) {
if (encryptionKeyId != null) {
if (validSignatureFingerprints.size() != 0) {
if (decryptionFingerprint != null) {
return State.signcrypt;
} else {
return State.sign;
}
} else {
if (encryptionKeyId != null) {
if (decryptionFingerprint != null) {
return State.crypt;
} else {
throw new IllegalStateException("OpenPGP message appears to be neither encrypted, " +

View file

@ -1,216 +0,0 @@
/**
*
* 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.
* 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;
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.backup.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;
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 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 MultiMap} containing all {@link OpenPgpV4Fingerprint}s of recipients which will
* be able to decrypt the 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 MissingOpenPgpPublicKeyException if any of the OpenPGP public keys whose {@link OpenPgpV4Fingerprint}
* is listed in {@code encryptionKeys} is not available.
* @throws SmackOpenPgpException in case of an OpenPGP error
* @throws IOException IO is dangerous
*/
byte[] signAndEncrypt(SigncryptElement element,
OpenPgpV4Fingerprint signingKey,
MultiMap<BareJid, OpenPgpV4Fingerprint> encryptionKeys)
throws MissingOpenPgpKeyPairException, MissingOpenPgpPublicKeyException, SmackOpenPgpException, IOException;
/**
* Sign a {@link SignElement} with the 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 byte array which contains the signed {@link SignElement}.
*
* @throws MissingOpenPgpKeyPairException if we don't have the key pair for the
* {@link OpenPgpV4Fingerprint} available.
* @throws IOException IO is dangerous
* @throws SmackOpenPgpException in case of an OpenPGP error
*/
byte[] sign(SignElement element, OpenPgpV4Fingerprint singingKeyFingerprint)
throws MissingOpenPgpKeyPairException, IOException, SmackOpenPgpException;
/**
* 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 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.
* @throws IOException IO is dangerous
* @throws SmackOpenPgpException in case of an OpenPGP error
*/
byte[] encrypt(CryptElement element, MultiMap<BareJid, OpenPgpV4Fingerprint> encryptionKeyFingerprints)
throws MissingOpenPgpPublicKeyException, IOException, SmackOpenPgpException;
/**
* 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}.
* @param sender sender of the message
* @param missingPublicKeyCallback callback to handle missing public keys
* @return byte array which contains the decrypted {@link OpenPgpContentElement}, as well as metadata.
*
* @throws MissingOpenPgpKeyPairException if we don't have an OpenPGP key pair available that to decrypt
* the message.
* @throws SmackOpenPgpException in case of an OpenPGP error
*/
DecryptedBytesAndMetadata decrypt(byte[] bytes, BareJid sender, SmackMissingOpenPgpPublicKeyCallback missingPublicKeyCallback)
throws MissingOpenPgpKeyPairException, SmackOpenPgpException;
/**
* Encrypt some data symmetrically using a password.
* @param bytes data
* @param password password
* @return encrypted data
* @throws SmackOpenPgpException in case of an OpenPGP error
* @throws IOException IO is dangerous
*/
byte[] symmetricallyEncryptWithPassword(byte[] bytes, String password) throws SmackOpenPgpException, IOException;
/**
* Decrypt a symmetrically encrypted array of data using the provided password.
*
* @param bytes symmetrically encrypted data
* @param password password for decryption
* @return decrypted data
* @throws SmackOpenPgpException if the password is incorrect
* @throws IOException io is dangerous
*/
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.
* @throws SmackOpenPgpException in case of an OpenPGP error
* @throws InvalidAlgorithmParameterException if invalid algorithm parameters are used for crypto
* @throws NoSuchAlgorithmException if the JVM is lacking support for a used algorithm
* @throws NoSuchProviderException if the JVM is missing a security provider
* @throws IOException IO is dangerous
*/
KeyBytesAndFingerprint generateOpenPgpKeyPair(BareJid owner)
throws SmackOpenPgpException, InvalidAlgorithmParameterException, NoSuchAlgorithmException,
NoSuchProviderException, IOException;
/**
* Import a public key. The bytes are expected to be decoded from base64.
*
* @param owner owner of the public key
* @param bytes byte representation of the publick key
*
* @return fingerprint of the imported public key
*
* @throws MissingUserIdOnKeyException if the key is missing a user id with {@code owner}.
* @throws IOException IO is dangerous
* @throws SmackOpenPgpException if an OpenPGP error occurs
*/
OpenPgpV4Fingerprint importPublicKey(BareJid owner, byte[] bytes)
throws MissingUserIdOnKeyException, IOException, SmackOpenPgpException;
/**
* Import a secret key. The bytes are expected to be decoded from base64.
*
* @param owner owner of the secret key
* @param bytes byte representation of the secret key
*
* @return fingerprint of the imported secret key
*
* @throws MissingUserIdOnKeyException if the key is missing a user-id of {@code owner}
* @throws SmackOpenPgpException in case of an OpenPGP error
* @throws IOException IO is dangerous
*/
OpenPgpV4Fingerprint importSecretKey(BareJid owner, byte[] bytes)
throws MissingUserIdOnKeyException, SmackOpenPgpException, IOException;
/**
* Import a secret key that belong to ourselves.
*
* @param bytes byte representation of the secret key.
*
* @return fingerprint of the imported secret key.
*
* @throws MissingUserIdOnKeyException if the secret key is missing a user-id with our jid
* @throws SmackOpenPgpException in case of an OpenPGP error
* @throws IOException IO is dangerous
*/
OpenPgpV4Fingerprint importSecretKey(byte[] bytes)
throws MissingUserIdOnKeyException, SmackOpenPgpException, IOException;
/**
* Return the underlying {@link OpenPgpStore}.
* @return store
*/
OpenPgpStore getStore();
}

View file

@ -1,32 +1,52 @@
/**
*
* 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;
import org.jivesoftware.smack.XMPPConnection;
import java.io.IOException;
import org.jivesoftware.smackx.ox.store.definition.OpenPgpStore;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
import org.jxmpp.jid.BareJid;
import org.pgpainless.pgpainless.key.OpenPgpV4Fingerprint;
public class OpenPgpSelf extends OpenPgpContact {
public OpenPgpSelf(OpenPgpProvider cryptoProvider, BareJid jid, XMPPConnection connection) {
super(cryptoProvider, jid, connection);
public OpenPgpSelf(BareJid jid, OpenPgpStore store) {
super(jid, store);
}
public OpenPgpV4Fingerprint getSigningKey() {
return cryptoProvider.getStore().getSigningKeyPairFingerprint();
public boolean hasSecretKeyAvailable() throws IOException, PGPException {
return getSecretKeys() != null;
}
public PGPSecretKeyRingCollection getSecretKeys() throws IOException, PGPException {
return store.getSecretKeysOf(jid);
}
public PGPSecretKeyRing getSigningKeyRing() throws IOException, PGPException {
PGPSecretKeyRingCollection secretKeyRings = getSecretKeys();
if (secretKeyRings == null) {
return null;
}
PGPSecretKeyRing signingKeyRing = null;
for (PGPSecretKeyRing ring : secretKeyRings) {
if (signingKeyRing == null) {
signingKeyRing = ring;
continue;
}
if (ring.getPublicKey().getCreationTime().after(signingKeyRing.getPublicKey().getCreationTime())) {
signingKeyRing = ring;
}
}
return signingKeyRing;
}
public OpenPgpV4Fingerprint getSigningKeyFingerprint() throws IOException, PGPException {
PGPSecretKeyRing signingKeyRing = getSigningKeyRing();
return signingKeyRing != null ? new OpenPgpV4Fingerprint(signingKeyRing.getPublicKey()) : null;
}
}

View file

@ -1,147 +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;
import java.util.Date;
import java.util.Map;
import java.util.Set;
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;
import org.jxmpp.jid.BareJid;
public interface OpenPgpStore {
/**
* Return the {@link OpenPgpV4Fingerprint} of the primary OpenPGP key pair.
* If multiple key pairs are available, only the primary key pair is used for signing.
* <br>
* Note: This method returns {@code null} if no key pair is available.
*
* @return fingerprint of the primary OpenPGP key pair.
*/
OpenPgpV4Fingerprint getSigningKeyPairFingerprint();
/**
* 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.
*
* @param fingerprint {@link OpenPgpV4Fingerprint} of the new primary key pair.
*/
void setSigningKeyPairFingerprint(OpenPgpV4Fingerprint fingerprint);
/**
* Return a {@link Set} containing the {@link OpenPgpV4Fingerprint}s of the master keys of all available
* OpenPGP key pairs of {@code owner}.
*
* @param owner owner.
* @return set of fingerprints of available OpenPGP key pairs master keys.
*
* @throws SmackOpenPgpException in case of an OpenPGP error
*/
Set<OpenPgpV4Fingerprint> getAvailableKeyPairFingerprints(BareJid owner) throws SmackOpenPgpException;
/**
* 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 (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 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 #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> getAnnouncedKeysFingerprints(BareJid contact);
/**
* Store a {@link Map} of a contacts fingerprints and publication dates in persistent storage.
*
* @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}.
*/
void setAnnouncedKeysFingerprints(BareJid contact, Map<OpenPgpV4Fingerprint, Date> fingerprints);
/**
* Return the a {@link Map} of {@link OpenPgpV4Fingerprint}s and the {@link Date}s of when they were last
* fetched from PubSub.
*
* @param owner owner of the keys
* @return {@link Map} of keys last revision dates.
*/
Map<OpenPgpV4Fingerprint, Date> getPubkeysLastRevisions(BareJid owner);
/**
* Set the last revision dates of all keys of a contact.
*
* @param owner owner of the keys
* @param revisionDates {@link Map} of {@link OpenPgpV4Fingerprint}s and the {@link Date}s of when they
* were last fetched from PubSub.
*/
void setPubkeysLastRevision(BareJid owner, Map<OpenPgpV4Fingerprint, Date> revisionDates);
/**
* Return a {@link MultiMap} which contains contacts and their trusted keys {@link OpenPgpV4Fingerprint}s.
*
* @return trusted fingerprints.
*/
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.
*
* @throws MissingOpenPgpPublicKeyException if the key does not exist.
*/
byte[] getPublicKeyRingBytes(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.
*
* @throws MissingOpenPgpKeyPairException if the secret key doesn't exist.
*/
byte[] getSecretKeyRingBytes(BareJid owner, OpenPgpV4Fingerprint fingerprint)
throws MissingOpenPgpKeyPairException;
}

View file

@ -16,7 +16,7 @@
*/
package org.jivesoftware.smackx.ox.callback;
import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint;
import org.pgpainless.pgpainless.key.OpenPgpV4Fingerprint;
public interface SecretKeyPassphraseCallback {

View file

@ -18,7 +18,7 @@ package org.jivesoftware.smackx.ox.callback.backup;
import java.util.Set;
import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint;
import org.pgpainless.pgpainless.key.OpenPgpV4Fingerprint;
/**
* Callback to allow the user to decide, which locally available secret keys they want to include in a backup.

View file

@ -18,7 +18,7 @@ package org.jivesoftware.smackx.ox.callback.backup;
import java.util.Set;
import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint;
import org.pgpainless.pgpainless.key.OpenPgpV4Fingerprint;
/**
* Callback to let the user decide which key from a backup they want to restore.

View file

@ -14,12 +14,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.ox.v2;
package org.jivesoftware.smackx.ox.crypto;
import java.io.IOException;
import java.util.Collection;
import org.jivesoftware.smackx.ox.OpenPgpContact;
import org.jivesoftware.smackx.ox.OpenPgpMessage;
import org.jivesoftware.smackx.ox.OpenPgpSelf;
import org.jivesoftware.smackx.ox.element.CryptElement;
import org.jivesoftware.smackx.ox.element.OpenPgpElement;
import org.jivesoftware.smackx.ox.element.SignElement;
@ -29,11 +31,15 @@ import org.bouncycastle.openpgp.PGPException;
public interface OpenPgpProvider {
OpenPgpElement signAndEncrypt(SigncryptElement element, OpenPgpSelf self, Collection<OpenPgpContact> recipients) throws IOException, PGPException;
OpenPgpElement signAndEncrypt(SigncryptElement element, OpenPgpSelf self, Collection<OpenPgpContact> recipients)
throws IOException, PGPException;
OpenPgpElement sign(SignElement element, OpenPgpSelf self) throws IOException, PGPException;
OpenPgpElement sign(SignElement element, OpenPgpSelf self)
throws IOException, PGPException;
OpenPgpElement encrypt(CryptElement element, OpenPgpSelf self, Collection<OpenPgpContact> recipients) throws IOException, PGPException;
OpenPgpElement encrypt(CryptElement element, OpenPgpSelf self, Collection<OpenPgpContact> recipients)
throws IOException, PGPException;
OpenPgpMessage decryptAndOrVerify(OpenPgpElement element, OpenPgpSelf self, OpenPgpContact sender) throws IOException, PGPException;
OpenPgpMessage decryptAndOrVerify(OpenPgpElement element, OpenPgpSelf self, OpenPgpContact sender)
throws IOException, PGPException;
}

View file

@ -1,4 +1,4 @@
package org.jivesoftware.smackx.ox.v2;
package org.jivesoftware.smackx.ox.crypto;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@ -7,7 +7,9 @@ import java.util.ArrayList;
import java.util.Collection;
import org.jivesoftware.smack.util.stringencoder.Base64;
import org.jivesoftware.smackx.ox.OpenPgpContact;
import org.jivesoftware.smackx.ox.OpenPgpMessage;
import org.jivesoftware.smackx.ox.OpenPgpSelf;
import org.jivesoftware.smackx.ox.element.CryptElement;
import org.jivesoftware.smackx.ox.element.OpenPgpElement;
import org.jivesoftware.smackx.ox.element.SignElement;
@ -122,6 +124,6 @@ public class PainlessOpenPgpProvider implements OpenPgpProvider {
PainlessResult info = cipherStream.getResult();
return new OpenPgpMessage(plainText.toByteArray(), new OpenPgpMessage.Metadata(
info.getDecryptionKeyId(), info.getVerifiedSignatureKeyIds()));
info.getDecryptionFingerprint(), info.getVerifiedSignaturesFingerprints()));
}
}

View file

@ -25,7 +25,8 @@ import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.NamedElement;
import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.util.XmlStringBuilder;
import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint;
import org.pgpainless.pgpainless.key.OpenPgpV4Fingerprint;
public final class PublicKeysListElement implements ExtensionElement {

View file

@ -16,9 +16,8 @@
*/
package org.jivesoftware.smackx.ox.exception;
import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint;
import org.jxmpp.jid.BareJid;
import org.pgpainless.pgpainless.key.OpenPgpV4Fingerprint;
/**
* Exception that gets thrown whenever an operation is missing an OpenPGP key pair.

View file

@ -16,9 +16,8 @@
*/
package org.jivesoftware.smackx.ox.exception;
import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint;
import org.jxmpp.jid.BareJid;
import org.pgpainless.pgpainless.key.OpenPgpV4Fingerprint;
/**
* Exception that gets thrown when an operation is missing an OpenPGP public key.

View file

@ -22,10 +22,10 @@ import static org.xmlpull.v1.XmlPullParser.START_TAG;
import java.util.Date;
import org.jivesoftware.smack.provider.ExtensionElementProvider;
import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint;
import org.jivesoftware.smackx.ox.element.PublicKeysListElement;
import org.jxmpp.util.XmppDateTime;
import org.pgpainless.pgpainless.key.OpenPgpV4Fingerprint;
import org.xmlpull.v1.XmlPullParser;
public final class PublicKeysListElementProvider extends ExtensionElementProvider<PublicKeysListElement> {

View file

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.ox.v2.store.abstr;
package org.jivesoftware.smackx.ox.store.abstr;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
@ -23,8 +23,7 @@ import java.security.NoSuchProviderException;
import java.util.HashMap;
import java.util.Map;
import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint;
import org.jivesoftware.smackx.ox.v2.store.definition.OpenPgpKeyStore;
import org.jivesoftware.smackx.ox.store.definition.OpenPgpKeyStore;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
@ -33,6 +32,7 @@ import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
import org.jxmpp.jid.BareJid;
import org.pgpainless.pgpainless.PGPainless;
import org.pgpainless.pgpainless.key.OpenPgpV4Fingerprint;
import org.pgpainless.pgpainless.key.generation.type.length.RsaLength;
public abstract class AbstractOpenPgpKeyStore implements OpenPgpKeyStore {

View file

@ -14,17 +14,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.ox.v2.store.abstr;
package org.jivesoftware.smackx.ox.store.abstr;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint;
import org.jivesoftware.smackx.ox.v2.store.definition.OpenPgpMetadataStore;
import org.jivesoftware.smackx.ox.store.definition.OpenPgpMetadataStore;
import org.jxmpp.jid.BareJid;
import org.pgpainless.pgpainless.key.OpenPgpV4Fingerprint;
public abstract class AbstractOpenPgpMetadataStore implements OpenPgpMetadataStore {

View file

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.ox.v2.store.abstr;
package org.jivesoftware.smackx.ox.store.abstr;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
@ -27,12 +27,11 @@ import java.util.Observable;
import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smackx.ox.OpenPgpContact;
import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint;
import org.jivesoftware.smackx.ox.callback.SecretKeyPassphraseCallback;
import org.jivesoftware.smackx.ox.v2.store.definition.OpenPgpKeyStore;
import org.jivesoftware.smackx.ox.v2.store.definition.OpenPgpMetadataStore;
import org.jivesoftware.smackx.ox.v2.store.definition.OpenPgpStore;
import org.jivesoftware.smackx.ox.v2.store.definition.OpenPgpTrustStore;
import org.jivesoftware.smackx.ox.store.definition.OpenPgpKeyStore;
import org.jivesoftware.smackx.ox.store.definition.OpenPgpMetadataStore;
import org.jivesoftware.smackx.ox.store.definition.OpenPgpStore;
import org.jivesoftware.smackx.ox.store.definition.OpenPgpTrustStore;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
@ -40,6 +39,7 @@ import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
import org.jxmpp.jid.BareJid;
import org.pgpainless.pgpainless.key.OpenPgpV4Fingerprint;
import org.pgpainless.pgpainless.key.protection.SecretKeyRingProtector;
public abstract class AbstractOpenPgpStore extends Observable implements OpenPgpStore {
@ -76,6 +76,11 @@ public abstract class AbstractOpenPgpStore extends Observable implements OpenPgp
this.unlocker = protector;
}
@Override
public SecretKeyRingProtector getKeyRingProtector() {
return unlocker;
}
@Override
public void setSecretKeyPassphraseCallback(SecretKeyPassphraseCallback callback) {
this.secretKeyPassphraseCallback = callback;

View file

@ -1,13 +1,13 @@
package org.jivesoftware.smackx.ox.v2.store.abstr;
package org.jivesoftware.smackx.ox.store.abstr;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint;
import org.jivesoftware.smackx.ox.v2.store.definition.OpenPgpTrustStore;
import org.jivesoftware.smackx.ox.store.definition.OpenPgpTrustStore;
import org.jxmpp.jid.BareJid;
import org.pgpainless.pgpainless.key.OpenPgpV4Fingerprint;
public abstract class AbstractOpenPgpTrustStore implements OpenPgpTrustStore {

View file

@ -15,6 +15,6 @@
* limitations under the License.
*/
/**
* Providers for XEP-0373: OpenPGP for XMPP using Bouncycastle.
* Abstract OpenPGP store implementations.
*/
package org.jivesoftware.smackx.ox.bouncycastle;
package org.jivesoftware.smackx.ox.store.abstr;

View file

@ -14,21 +14,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.ox.v2.store.definition;
package org.jivesoftware.smackx.ox.store.definition;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
import org.jxmpp.jid.BareJid;
import org.pgpainless.pgpainless.key.OpenPgpV4Fingerprint;
public interface OpenPgpKeyStore {

View file

@ -14,15 +14,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.ox.v2.store.definition;
package org.jivesoftware.smackx.ox.store.definition;
import java.io.IOException;
import java.util.Date;
import java.util.Map;
import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint;
import org.jxmpp.jid.BareJid;
import org.pgpainless.pgpainless.key.OpenPgpV4Fingerprint;
public interface OpenPgpMetadataStore {

View file

@ -1,4 +1,4 @@
package org.jivesoftware.smackx.ox.v2.store.definition;
package org.jivesoftware.smackx.ox.store.definition;
import org.jivesoftware.smackx.ox.OpenPgpContact;
import org.jivesoftware.smackx.ox.callback.SecretKeyPassphraseCallback;

View file

@ -14,13 +14,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.ox.v2.store.definition;
package org.jivesoftware.smackx.ox.store.definition;
import java.io.IOException;
import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint;
import org.jxmpp.jid.BareJid;
import org.pgpainless.pgpainless.key.OpenPgpV4Fingerprint;
public interface OpenPgpTrustStore {

View file

@ -15,6 +15,6 @@
* limitations under the License.
*/
/**
* Second iteration.
* OpenPgp store class definitions.
*/
package org.jivesoftware.smackx.ox.v2.store.filebased;
package org.jivesoftware.smackx.ox.store.definition;

View file

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.ox.v2.store.filebased;
package org.jivesoftware.smackx.ox.store.filebased;
import static org.jivesoftware.smackx.ox.util.FileUtils.prepareFileInputStream;
import static org.jivesoftware.smackx.ox.util.FileUtils.prepareFileOutputStream;
@ -26,7 +26,7 @@ import java.io.IOException;
import java.io.OutputStream;
import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smackx.ox.v2.store.abstr.AbstractOpenPgpKeyStore;
import org.jivesoftware.smackx.ox.store.abstr.AbstractOpenPgpKeyStore;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;

View file

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.ox.v2.store.filebased;
package org.jivesoftware.smackx.ox.store.filebased;
import java.io.BufferedReader;
import java.io.BufferedWriter;
@ -28,12 +28,13 @@ import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint;
import org.jivesoftware.smackx.ox.store.abstr.AbstractOpenPgpMetadataStore;
import org.jivesoftware.smackx.ox.util.Util;
import org.jivesoftware.smackx.ox.v2.store.abstr.AbstractOpenPgpMetadataStore;
import org.bouncycastle.openpgp.PGPException;
import org.jxmpp.jid.BareJid;
import org.jxmpp.util.XmppDateTime;
import org.pgpainless.pgpainless.key.OpenPgpV4Fingerprint;
public class FileBasedOpenPgpMetadataStore extends AbstractOpenPgpMetadataStore {
@ -81,7 +82,7 @@ public class FileBasedOpenPgpMetadataStore extends AbstractOpenPgpMetadataStore
OpenPgpV4Fingerprint fingerprint = new OpenPgpV4Fingerprint(split[0]);
Date date = XmppDateTime.parseXEP0082Date(split[1]);
fingerprintDateMap.put(fingerprint, date);
} catch (ParseException e) {
} catch (PGPException | ParseException e) {
LOGGER.log(Level.WARNING, "Error parsing fingerprint/date touple in line " + lineNr +
" of file " + source.getAbsolutePath(), e);
}

View file

@ -14,12 +14,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.ox.v2.store.filebased;
package org.jivesoftware.smackx.ox.store.filebased;
import java.io.File;
import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smackx.ox.v2.store.abstr.AbstractOpenPgpStore;
import org.jivesoftware.smackx.ox.store.abstr.AbstractOpenPgpStore;
import org.jxmpp.jid.BareJid;

View file

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.ox.v2.store.filebased;
package org.jivesoftware.smackx.ox.store.filebased;
import java.io.BufferedReader;
import java.io.BufferedWriter;
@ -25,11 +25,11 @@ import java.nio.file.Files;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint;
import org.jivesoftware.smackx.ox.store.abstr.AbstractOpenPgpTrustStore;
import org.jivesoftware.smackx.ox.util.Util;
import org.jivesoftware.smackx.ox.v2.store.abstr.AbstractOpenPgpTrustStore;
import org.jxmpp.jid.BareJid;
import org.pgpainless.pgpainless.key.OpenPgpV4Fingerprint;
public class FileBasedOpenPgpTrustStore extends AbstractOpenPgpTrustStore {

View file

@ -15,6 +15,6 @@
* limitations under the License.
*/
/**
* Second iteration.
* File based store implementations.
*/
package org.jivesoftware.smackx.ox.v2;
package org.jivesoftware.smackx.ox.store.filebased;

View file

@ -15,6 +15,6 @@
* limitations under the License.
*/
/**
* Second iteration.
* OpenPGP store implementations.
*/
package org.jivesoftware.smackx.ox.v2.store;
package org.jivesoftware.smackx.ox.store;

View file

@ -1,40 +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.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;
}
}

View file

@ -31,7 +31,6 @@ import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.StanzaError;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.ox.OpenPgpManager;
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;
@ -46,6 +45,7 @@ import org.jivesoftware.smackx.pubsub.PubSubManager;
import org.jivesoftware.smackx.xdata.packet.DataForm;
import org.jxmpp.jid.BareJid;
import org.pgpainless.pgpainless.key.OpenPgpV4Fingerprint;
public class PubSubDelegate {

View file

@ -16,21 +16,18 @@
*/
package org.jivesoftware.smackx.ox.util;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.Set;
import org.jivesoftware.smack.util.stringencoder.Base64;
import org.jivesoftware.smackx.ox.OpenPgpProvider;
import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint;
import org.jivesoftware.smackx.ox.crypto.OpenPgpProvider;
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.MissingUserIdOnKeyException;
import org.jivesoftware.smackx.ox.exception.SmackOpenPgpException;
import org.jxmpp.jid.BareJid;
import org.pgpainless.pgpainless.key.OpenPgpV4Fingerprint;
public class SecretKeyBackupHelper {
@ -66,6 +63,7 @@ public class SecretKeyBackupHelper {
BareJid owner,
Set<OpenPgpV4Fingerprint> fingerprints,
String backupCode) throws SmackOpenPgpException, IOException {
/*
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
for (OpenPgpV4Fingerprint fingerprint : fingerprints) {
try {
@ -76,18 +74,22 @@ public class SecretKeyBackupHelper {
}
}
return createSecretkeyElement(provider, buffer.toByteArray(), backupCode);
*/
return null;
}
public static SecretkeyElement createSecretkeyElement(OpenPgpProvider provider,
byte[] keys,
String backupCode)
throws SmackOpenPgpException, IOException {
byte[] encrypted = provider.symmetricallyEncryptWithPassword(keys, backupCode);
return new SecretkeyElement(Base64.encode(encrypted));
// byte[] encrypted = provider.symmetricallyEncryptWithPassword(keys, backupCode);
// return new SecretkeyElement(Base64.encode(encrypted));
return null;
}
public static OpenPgpV4Fingerprint restoreSecretKeyBackup(OpenPgpProvider provider, SecretkeyElement backup, String backupCode)
throws InvalidBackupCodeException, IOException, MissingUserIdOnKeyException, SmackOpenPgpException {
/*
byte[] encrypted = Base64.decode(backup.getB64Data());
byte[] decrypted;
@ -98,5 +100,7 @@ public class SecretKeyBackupHelper {
}
return provider.importSecretKey(decrypted);
*/
return null;
}
}

View file

@ -1,47 +0,0 @@
package org.jivesoftware.smackx.ox.v2;
import java.io.IOException;
import java.util.Set;
import java.util.logging.Logger;
import org.jivesoftware.smackx.ox.v2.store.definition.OpenPgpStore;
import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.jxmpp.jid.BareJid;
public class OpenPgpContact {
private final Logger LOGGER;
protected final BareJid jid;
protected final OpenPgpStore store;
public OpenPgpContact(BareJid jid, OpenPgpStore store) {
this.jid = jid;
this.store = store;
LOGGER = Logger.getLogger(OpenPgpContact.class.getName() + ":" + jid.toString());
}
public PGPPublicKeyRingCollection getAnyPublicKeys() throws IOException, PGPException {
return store.getPublicKeysOf(jid);
}
public PGPPublicKeyRingCollection getAnnouncedPublicKeys() throws IOException, PGPException {
PGPPublicKeyRingCollection anyKeys = getAnyPublicKeys();
Set<OpenPgpV4Fingerprint> announced = store.getAnnouncedFingerprintsOf(jid).keySet();
PGPPublicKeyRingCollection announcedKeysCollection = anyKeys;
for (PGPPublicKeyRing ring : anyKeys) {
OpenPgpV4Fingerprint fingerprint = new OpenPgpV4Fingerprint(ring.getPublicKey());
if (!announced.contains(fingerprint)) {
announcedKeysCollection = PGPPublicKeyRingCollection.removePublicKeyRing(announcedKeysCollection, ring);
}
}
return announcedKeysCollection;
}
}

View file

@ -1,52 +0,0 @@
package org.jivesoftware.smackx.ox.v2;
import java.io.IOException;
import org.jivesoftware.smackx.ox.v2.store.definition.OpenPgpStore;
import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
import org.jxmpp.jid.BareJid;
public class OpenPgpSelf extends OpenPgpContact {
public OpenPgpSelf(BareJid jid, OpenPgpStore store) {
super(jid, store);
}
public boolean hasSecretKeyAvailable() throws IOException, PGPException {
return getSecretKeys() != null;
}
public PGPSecretKeyRingCollection getSecretKeys() throws IOException, PGPException {
return store.getSecretKeysOf(jid);
}
public PGPSecretKeyRing getSigningKeyRing() throws IOException, PGPException {
PGPSecretKeyRingCollection secretKeyRings = getSecretKeys();
if (secretKeyRings == null) {
return null;
}
PGPSecretKeyRing signingKeyRing = null;
for (PGPSecretKeyRing ring : secretKeyRings) {
if (signingKeyRing == null) {
signingKeyRing = ring;
continue;
}
if (ring.getPublicKey().getCreationTime().after(signingKeyRing.getPublicKey().getCreationTime())) {
signingKeyRing = ring;
}
}
return signingKeyRing;
}
public OpenPgpV4Fingerprint getSigningKeyFingerprint() throws IOException, PGPException {
PGPSecretKeyRing signingKeyRing = getSigningKeyRing();
return signingKeyRing != null ? new OpenPgpV4Fingerprint(signingKeyRing.getPublicKey()) : null;
}
}

View file

@ -1,58 +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;
import static junit.framework.TestCase.assertEquals;
import org.jivesoftware.smack.test.util.SmackTestSuite;
import org.junit.Test;
public class OpenPgpV4FingerprintTest extends SmackTestSuite {
@Test(expected = IllegalArgumentException.class)
public void fpTooShort() {
String fp = "484f57414c495645"; // Asking Mark
new OpenPgpV4Fingerprint(fp);
}
@Test(expected = IllegalArgumentException.class)
public void invalidHexTest() {
String fp = "UNFORTUNATELYTHISISNOVALIDHEXADECIMALDOH";
new OpenPgpV4Fingerprint(fp);
}
@Test
public void validFingerprintTest() {
String fp = "4A4F48414E4E53454E2049532041204E45524421";
OpenPgpV4Fingerprint finger = new OpenPgpV4Fingerprint(fp);
assertEquals(fp, finger.toString());
}
@Test
public void convertsToUpperCaseTest() {
String fp = "444f4e5420552048415645204120484f4242593f";
OpenPgpV4Fingerprint finger = new OpenPgpV4Fingerprint(fp);
assertEquals("444F4E5420552048415645204120484F4242593F", finger.toString());
}
@Test
public void equalsOtherFingerprintTest() {
OpenPgpV4Fingerprint finger = new OpenPgpV4Fingerprint("5448452043414b452049532041204c4945212121");
assertEquals(finger, new OpenPgpV4Fingerprint("5448452043414B452049532041204C4945212121"));
}
}

View file

@ -21,12 +21,14 @@ import static junit.framework.TestCase.assertEquals;
import org.jivesoftware.smack.test.util.SmackTestSuite;
import org.jivesoftware.smackx.ox.util.PubSubDelegate;
import org.bouncycastle.openpgp.PGPException;
import org.junit.Test;
import org.pgpainless.pgpainless.key.OpenPgpV4Fingerprint;
public class PubSubDelegateTest extends SmackTestSuite {
@Test
public void pubkeyNodeNameTest() {
public void pubkeyNodeNameTest() throws PGPException {
OpenPgpV4Fingerprint fingerprint = new OpenPgpV4Fingerprint("486f7065207520646f6e2068617665204f43640a");
assertEquals("urn:xmpp:openpgp:0:public-keys:486F7065207520646F6E2068617665204F43640A",
PubSubDelegate.PEP_NODE_PUBLIC_KEY(fingerprint));

View file

@ -17,7 +17,6 @@
package org.jivesoftware.smackx.ox;
import static junit.framework.TestCase.assertEquals;
import static org.custommonkey.xmlunit.XMLAssert.assertXMLEqual;
import java.util.Date;
@ -27,8 +26,10 @@ import org.jivesoftware.smack.test.util.TestUtils;
import org.jivesoftware.smackx.ox.element.PublicKeysListElement;
import org.jivesoftware.smackx.ox.provider.PublicKeysListElementProvider;
import org.bouncycastle.openpgp.PGPException;
import org.junit.Test;
import org.jxmpp.util.XmppDateTime;
import org.pgpainless.pgpainless.key.OpenPgpV4Fingerprint;
import org.xmlpull.v1.XmlPullParser;
public class PublicKeysListElementTest extends SmackTestSuite {
@ -70,7 +71,7 @@ public class PublicKeysListElementTest extends SmackTestSuite {
}
@Test
public void listBuilderRefusesDuplicatesTest() {
public void listBuilderRefusesDuplicatesTest() throws PGPException {
PublicKeysListElement.Builder builder = PublicKeysListElement.builder();
String fp40 = "49545320414c4c2041424f555420444120484558";
Date oneDate = new Date(12337883234L);