diff --git a/smack-openpgp-bouncycastle/build.gradle b/smack-openpgp-bouncycastle/build.gradle index 3a66cd602..1ff1b2110 100644 --- a/smack-openpgp-bouncycastle/build.gradle +++ b/smack-openpgp-bouncycastle/build.gradle @@ -6,7 +6,6 @@ Smack API for OpenPGP using Bouncycastle.""" dependencies { compile project(':smack-core') compile project(':smack-openpgp') - compile 'org.bouncycastle:bcpg-jdk15on:1.59' compile 'de.vanitasvitae.crypto:pgpainless:0.1-SNAPSHOT' testCompile project(path: ":smack-core", configuration: "testRuntime") testCompile project(path: ":smack-core", configuration: "archives") 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 index d29a92bae..4893aa718 100644 --- 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 @@ -24,10 +24,14 @@ import java.io.OutputStream; import java.security.InvalidAlgorithmParameterException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; +import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; +import java.util.List; import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; import org.jivesoftware.smack.util.MultiMap; import org.jivesoftware.smackx.ox.OpenPgpProvider; @@ -45,9 +49,10 @@ 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.algorithm.SymmetricKeyAlgorithm; 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.SecretKeyRingProtector; import de.vanitasvitae.crypto.pgpainless.key.generation.type.length.RsaLength; import de.vanitasvitae.crypto.pgpainless.util.BCUtil; import org.bouncycastle.openpgp.PGPException; @@ -63,6 +68,8 @@ import org.jxmpp.jid.BareJid; public class PainlessOpenPgpProvider implements OpenPgpProvider { + private static final Logger LOGGER = Logger.getLogger(PainlessOpenPgpProvider.class.getName()); + private final PainlessOpenPgpStore store; private final BareJid owner; @@ -78,28 +85,62 @@ public class PainlessOpenPgpProvider implements OpenPgpProvider { throws MissingOpenPgpKeyPairException, MissingOpenPgpPublicKeyException, SmackOpenPgpException, IOException { - PGPPublicKeyRing[] validEncryptionKeys = getEncryptionKeys(encryptionKeys); - PGPSecretKeyRing signingKeyRing = getSigningKey(signingKey); + PGPSecretKeyRing secretKeyRing; + SecretKeyRingProtector protector = getStore().getSecretKeyProtector(); - InputStream fromPlain = element.toInputStream(); - ByteArrayOutputStream encrypted = new ByteArrayOutputStream(); - OutputStream encryptor; try { - encryptor = PGPainless.createEncryptor() - .onOutputStream(encrypted) - .toRecipients(validEncryptionKeys) - .usingSecureAlgorithms() - .signWith(store.getSecretKeyProtector(), signingKeyRing) - .noArmor(); + secretKeyRing = getStore().getSecretKeyRings(owner).getSecretKeyRing(signingKey.getKeyId()); } catch (PGPException e) { + LOGGER.log(Level.INFO, "Could not get secret key with id " + Long.toHexString(signingKey.getKeyId()), e); + throw new MissingOpenPgpKeyPairException(owner, signingKey, e); + } + + MultiMap publicKeyRingMultiMap = new MultiMap<>(); + for (BareJid jid : encryptionKeys.keySet()) { + try { + PGPPublicKeyRingCollection publicKeyRings = getStore().getPublicKeyRings(jid); + for (OpenPgpV4Fingerprint f : encryptionKeys.getAll(jid)) { + PGPPublicKeyRing ring = publicKeyRings.getPublicKeyRing(f.getKeyId()); + if (ring != null) { + publicKeyRingMultiMap.put(jid, ring); + } + } + } catch (PGPException e) { + LOGGER.log(Level.INFO, "Could get public keys of " + jid.toString()); + throw new MissingOpenPgpPublicKeyException(owner, encryptionKeys.getFirst(jid)); + } + } + + return signAndEncryptImpl(element, secretKeyRing, protector, publicKeyRingMultiMap); + } + + byte[] signAndEncryptImpl(SigncryptElement element, + PGPSecretKeyRing signingKey, + SecretKeyRingProtector secretKeyRingProtector, + MultiMap encryptionKeys) + throws SmackOpenPgpException, IOException { + InputStream fromPlain = element.toInputStream(); + ByteArrayOutputStream encryptedBytes = new ByteArrayOutputStream(); + + OutputStream toEncrypted; + try { + toEncrypted = PGPainless.createEncryptor() + .onOutputStream(encryptedBytes) + .toRecipients(new ArrayList<>(encryptionKeys.values()).toArray(new PGPPublicKeyRing[]{})) + .usingSecureAlgorithms() + .signWith(secretKeyRingProtector, signingKey) + .noArmor(); + } catch (PGPException | IOException e) { throw new SmackOpenPgpException(e); } - Streams.pipeAll(fromPlain, encryptor); - fromPlain.close(); - encryptor.close(); + Streams.pipeAll(fromPlain, toEncrypted); + toEncrypted.flush(); + toEncrypted.close(); - return encrypted.toByteArray(); + encryptedBytes.close(); + + return encryptedBytes.toByteArray(); } @Override @@ -161,31 +202,73 @@ public class PainlessOpenPgpProvider implements OpenPgpProvider { @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.getSecretKeyRings(owner), store.getSecretKeyProtector()) - .verifyWith(trustedKeyIds, senderKeys) - .handleMissingPublicKeysWith(new MissingPublicKeyCallback() { - @Override - public void onMissingPublicKeyEncountered(Long aLong) { - } - }) - .build(); + PGPSecretKeyRingCollection secretKeyRings; + try { + secretKeyRings = getStore().getSecretKeyRings(owner); } catch (PGPException e) { + LOGGER.log(Level.INFO, "Could not get secret keys of user " + owner); + throw new MissingOpenPgpKeyPairException(owner, getStore().getPrimaryOpenPgpKeyPairFingerprint()); + } + + SecretKeyRingProtector protector = getStore().getSecretKeyProtector(); + + List trustedFingerprints = getStore().getAllContactsTrustedFingerprints().getAll(sender); + Set trustedKeyIds = new HashSet<>(); + for (OpenPgpV4Fingerprint fingerprint : trustedFingerprints) { + trustedKeyIds.add(fingerprint.getKeyId()); + } + + PGPPublicKeyRingCollection publicKeyRings; + try { + publicKeyRings = getStore().getPublicKeyRings(sender); + } catch (PGPException e) { + LOGGER.log(Level.INFO, "Could not get public keys of sender " + sender.toString(), e); + if (missingPublicKeyCallback != null) { + // TODO: Handle missing key + } throw new SmackOpenPgpException(e); } - Streams.pipeAll(decryptionStream, toPlain); + + Iterator iterator = publicKeyRings.getKeyRings(); + Set trustedKeys = new HashSet<>(); + while (iterator.hasNext()) { + PGPPublicKeyRing ring = iterator.next(); + if (trustedKeyIds.contains(ring.getPublicKey().getKeyID())) { + trustedKeys.add(ring); + } + } + + return decryptImpl(bytes, secretKeyRings, protector, trustedKeyIds, trustedKeys); + } + + DecryptedBytesAndMetadata decryptImpl(byte[] bytes, PGPSecretKeyRingCollection decryptionKeys, + SecretKeyRingProtector protector, + Set trustedKeyIds, + Set verificationKeys) + throws SmackOpenPgpException, IOException { + + InputStream encryptedBytes = new ByteArrayInputStream(bytes); + ByteArrayOutputStream toPlain = new ByteArrayOutputStream(); + DecryptionStream fromEncrypted; + try { + fromEncrypted = PGPainless.createDecryptor() + .onInputStream(encryptedBytes) + .decryptWith(decryptionKeys, protector) + .verifyWith(trustedKeyIds, verificationKeys) + .ignoreMissingPublicKeys() + .build(); + } catch (IOException | PGPException e) { + throw new SmackOpenPgpException(e); + } + + Streams.pipeAll(fromEncrypted, toPlain); + fromEncrypted.close(); - decryptionStream.close(); - - PainlessResult result = decryptionStream.getResult(); + toPlain.flush(); + toPlain.close(); + PainlessResult result = fromEncrypted.getResult(); return new DecryptedBytesAndMetadata(toPlain.toByteArray(), result.getVerifiedSignatureKeyIds(), result.getDecryptionKeyId()); @@ -195,7 +278,7 @@ public class PainlessOpenPgpProvider implements OpenPgpProvider { public byte[] symmetricallyEncryptWithPassword(byte[] bytes, String password) throws SmackOpenPgpException, IOException { try { - return PGPainless.encryptWithPassword(bytes, password.toCharArray()); + return PGPainless.encryptWithPassword(bytes, password.toCharArray(), SymmetricKeyAlgorithm.AES_256); } catch (PGPException e) { throw new SmackOpenPgpException(e); } 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 b6079a78b..9370dfd70 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 @@ -147,13 +147,11 @@ public final class OXInstantMessagingManager extends Manager { } OpenPgpProvider provider = openPgpManager.getOpenPgpProvider(); + byte[] decoded = Base64.decode(element.getEncryptedBase64MessageContent()); try { OpenPgpEncryptedChat encryptedChat = chatWith(from); - DecryptedBytesAndMetadata decryptedBytes = provider.decrypt(Base64.decode( - element.getEncryptedBase64MessageContent()), - from.asBareJid(), - null); + DecryptedBytesAndMetadata decryptedBytes = provider.decrypt(decoded, from.asBareJid(), null); OpenPgpMessage openPgpMessage = new OpenPgpMessage(decryptedBytes.getBytes(), new OpenPgpMessage.Metadata(decryptedBytes.getDecryptionKey(), diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/chat/OpenPgpMessage.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/chat/OpenPgpMessage.java index ef46fd559..c2e96c399 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/chat/OpenPgpMessage.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/chat/OpenPgpMessage.java @@ -22,7 +22,6 @@ 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; @@ -70,7 +69,7 @@ public class OpenPgpMessage { } public OpenPgpMessage(byte[] bytes, Metadata metadata) { - this.element = new String(Base64.decode(bytes), Charset.forName("UTF-8")); + this.element = new String(bytes, Charset.forName("UTF-8")); this.state = metadata.getState(); }