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