mirror of
https://github.com/vanitasvitae/Smack.git
synced 2024-11-23 20:42:06 +01:00
Fix decryption error
This commit is contained in:
parent
800955d5a8
commit
63e98cb4d6
4 changed files with 122 additions and 43 deletions
|
@ -6,7 +6,6 @@ Smack API for OpenPGP using Bouncycastle."""
|
||||||
dependencies {
|
dependencies {
|
||||||
compile project(':smack-core')
|
compile project(':smack-core')
|
||||||
compile project(':smack-openpgp')
|
compile project(':smack-openpgp')
|
||||||
compile 'org.bouncycastle:bcpg-jdk15on:1.59'
|
|
||||||
compile 'de.vanitasvitae.crypto:pgpainless:0.1-SNAPSHOT'
|
compile 'de.vanitasvitae.crypto:pgpainless:0.1-SNAPSHOT'
|
||||||
testCompile project(path: ":smack-core", configuration: "testRuntime")
|
testCompile project(path: ":smack-core", configuration: "testRuntime")
|
||||||
testCompile project(path: ":smack-core", configuration: "archives")
|
testCompile project(path: ":smack-core", configuration: "archives")
|
||||||
|
|
|
@ -24,10 +24,14 @@ import java.io.OutputStream;
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.NoSuchProviderException;
|
import java.security.NoSuchProviderException;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import org.jivesoftware.smack.util.MultiMap;
|
import org.jivesoftware.smack.util.MultiMap;
|
||||||
import org.jivesoftware.smackx.ox.OpenPgpProvider;
|
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 org.jivesoftware.smackx.ox.util.KeyBytesAndFingerprint;
|
||||||
|
|
||||||
import de.vanitasvitae.crypto.pgpainless.PGPainless;
|
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.DecryptionStream;
|
||||||
import de.vanitasvitae.crypto.pgpainless.decryption_verification.MissingPublicKeyCallback;
|
|
||||||
import de.vanitasvitae.crypto.pgpainless.decryption_verification.PainlessResult;
|
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.key.generation.type.length.RsaLength;
|
||||||
import de.vanitasvitae.crypto.pgpainless.util.BCUtil;
|
import de.vanitasvitae.crypto.pgpainless.util.BCUtil;
|
||||||
import org.bouncycastle.openpgp.PGPException;
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
|
@ -63,6 +68,8 @@ import org.jxmpp.jid.BareJid;
|
||||||
|
|
||||||
public class PainlessOpenPgpProvider implements OpenPgpProvider {
|
public class PainlessOpenPgpProvider implements OpenPgpProvider {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(PainlessOpenPgpProvider.class.getName());
|
||||||
|
|
||||||
private final PainlessOpenPgpStore store;
|
private final PainlessOpenPgpStore store;
|
||||||
private final BareJid owner;
|
private final BareJid owner;
|
||||||
|
|
||||||
|
@ -78,28 +85,62 @@ public class PainlessOpenPgpProvider implements OpenPgpProvider {
|
||||||
throws MissingOpenPgpKeyPairException, MissingOpenPgpPublicKeyException, SmackOpenPgpException,
|
throws MissingOpenPgpKeyPairException, MissingOpenPgpPublicKeyException, SmackOpenPgpException,
|
||||||
IOException {
|
IOException {
|
||||||
|
|
||||||
PGPPublicKeyRing[] validEncryptionKeys = getEncryptionKeys(encryptionKeys);
|
PGPSecretKeyRing secretKeyRing;
|
||||||
PGPSecretKeyRing signingKeyRing = getSigningKey(signingKey);
|
SecretKeyRingProtector protector = getStore().getSecretKeyProtector();
|
||||||
|
|
||||||
InputStream fromPlain = element.toInputStream();
|
|
||||||
ByteArrayOutputStream encrypted = new ByteArrayOutputStream();
|
|
||||||
OutputStream encryptor;
|
|
||||||
try {
|
try {
|
||||||
encryptor = PGPainless.createEncryptor()
|
secretKeyRing = getStore().getSecretKeyRings(owner).getSecretKeyRing(signingKey.getKeyId());
|
||||||
.onOutputStream(encrypted)
|
|
||||||
.toRecipients(validEncryptionKeys)
|
|
||||||
.usingSecureAlgorithms()
|
|
||||||
.signWith(store.getSecretKeyProtector(), signingKeyRing)
|
|
||||||
.noArmor();
|
|
||||||
} catch (PGPException e) {
|
} catch (PGPException e) {
|
||||||
|
LOGGER.log(Level.INFO, "Could not get secret key with id " + Long.toHexString(signingKey.getKeyId()), e);
|
||||||
|
throw new MissingOpenPgpKeyPairException(owner, signingKey, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
MultiMap<BareJid, PGPPublicKeyRing> publicKeyRingMultiMap = new MultiMap<>();
|
||||||
|
for (BareJid jid : encryptionKeys.keySet()) {
|
||||||
|
try {
|
||||||
|
PGPPublicKeyRingCollection publicKeyRings = getStore().getPublicKeyRings(jid);
|
||||||
|
for (OpenPgpV4Fingerprint f : encryptionKeys.getAll(jid)) {
|
||||||
|
PGPPublicKeyRing ring = publicKeyRings.getPublicKeyRing(f.getKeyId());
|
||||||
|
if (ring != null) {
|
||||||
|
publicKeyRingMultiMap.put(jid, ring);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (PGPException e) {
|
||||||
|
LOGGER.log(Level.INFO, "Could get public keys of " + jid.toString());
|
||||||
|
throw new MissingOpenPgpPublicKeyException(owner, encryptionKeys.getFirst(jid));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return signAndEncryptImpl(element, secretKeyRing, protector, publicKeyRingMultiMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] signAndEncryptImpl(SigncryptElement element,
|
||||||
|
PGPSecretKeyRing signingKey,
|
||||||
|
SecretKeyRingProtector secretKeyRingProtector,
|
||||||
|
MultiMap<BareJid, PGPPublicKeyRing> encryptionKeys)
|
||||||
|
throws SmackOpenPgpException, IOException {
|
||||||
|
InputStream fromPlain = element.toInputStream();
|
||||||
|
ByteArrayOutputStream encryptedBytes = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
OutputStream toEncrypted;
|
||||||
|
try {
|
||||||
|
toEncrypted = PGPainless.createEncryptor()
|
||||||
|
.onOutputStream(encryptedBytes)
|
||||||
|
.toRecipients(new ArrayList<>(encryptionKeys.values()).toArray(new PGPPublicKeyRing[]{}))
|
||||||
|
.usingSecureAlgorithms()
|
||||||
|
.signWith(secretKeyRingProtector, signingKey)
|
||||||
|
.noArmor();
|
||||||
|
} catch (PGPException | IOException e) {
|
||||||
throw new SmackOpenPgpException(e);
|
throw new SmackOpenPgpException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
Streams.pipeAll(fromPlain, encryptor);
|
Streams.pipeAll(fromPlain, toEncrypted);
|
||||||
fromPlain.close();
|
toEncrypted.flush();
|
||||||
encryptor.close();
|
toEncrypted.close();
|
||||||
|
|
||||||
return encrypted.toByteArray();
|
encryptedBytes.close();
|
||||||
|
|
||||||
|
return encryptedBytes.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -161,31 +202,73 @@ public class PainlessOpenPgpProvider implements OpenPgpProvider {
|
||||||
@Override
|
@Override
|
||||||
public DecryptedBytesAndMetadata decrypt(byte[] bytes, BareJid sender, final SmackMissingOpenPgpPublicKeyCallback missingPublicKeyCallback)
|
public DecryptedBytesAndMetadata decrypt(byte[] bytes, BareJid sender, final SmackMissingOpenPgpPublicKeyCallback missingPublicKeyCallback)
|
||||||
throws MissingOpenPgpKeyPairException, SmackOpenPgpException, IOException {
|
throws MissingOpenPgpKeyPairException, SmackOpenPgpException, IOException {
|
||||||
Set<Long> trustedKeyIds = new HashSet<>();
|
|
||||||
Set<PGPPublicKeyRing> senderKeys = new HashSet<>();
|
|
||||||
InputStream fromEncrypted = new ByteArrayInputStream(bytes);
|
|
||||||
ByteArrayOutputStream toPlain = new ByteArrayOutputStream();
|
|
||||||
DecryptionStream decryptionStream;
|
|
||||||
try {
|
|
||||||
decryptionStream = PGPainless.createDecryptor().onInputStream(fromEncrypted)
|
|
||||||
.decryptWith(store.getSecretKeyRings(owner), store.getSecretKeyProtector())
|
|
||||||
.verifyWith(trustedKeyIds, senderKeys)
|
|
||||||
.handleMissingPublicKeysWith(new MissingPublicKeyCallback() {
|
|
||||||
@Override
|
|
||||||
public void onMissingPublicKeyEncountered(Long aLong) {
|
|
||||||
|
|
||||||
}
|
PGPSecretKeyRingCollection secretKeyRings;
|
||||||
})
|
try {
|
||||||
.build();
|
secretKeyRings = getStore().getSecretKeyRings(owner);
|
||||||
} catch (PGPException e) {
|
} 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<OpenPgpV4Fingerprint> trustedFingerprints = getStore().getAllContactsTrustedFingerprints().getAll(sender);
|
||||||
|
Set<Long> trustedKeyIds = new HashSet<>();
|
||||||
|
for (OpenPgpV4Fingerprint fingerprint : trustedFingerprints) {
|
||||||
|
trustedKeyIds.add(fingerprint.getKeyId());
|
||||||
|
}
|
||||||
|
|
||||||
|
PGPPublicKeyRingCollection publicKeyRings;
|
||||||
|
try {
|
||||||
|
publicKeyRings = getStore().getPublicKeyRings(sender);
|
||||||
|
} catch (PGPException 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);
|
throw new SmackOpenPgpException(e);
|
||||||
}
|
}
|
||||||
Streams.pipeAll(decryptionStream, toPlain);
|
|
||||||
|
Iterator<PGPPublicKeyRing> iterator = publicKeyRings.getKeyRings();
|
||||||
|
Set<PGPPublicKeyRing> trustedKeys = new HashSet<>();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
PGPPublicKeyRing ring = iterator.next();
|
||||||
|
if (trustedKeyIds.contains(ring.getPublicKey().getKeyID())) {
|
||||||
|
trustedKeys.add(ring);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return decryptImpl(bytes, secretKeyRings, protector, trustedKeyIds, trustedKeys);
|
||||||
|
}
|
||||||
|
|
||||||
|
DecryptedBytesAndMetadata decryptImpl(byte[] bytes, PGPSecretKeyRingCollection decryptionKeys,
|
||||||
|
SecretKeyRingProtector protector,
|
||||||
|
Set<Long> trustedKeyIds,
|
||||||
|
Set<PGPPublicKeyRing> 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();
|
fromEncrypted.close();
|
||||||
decryptionStream.close();
|
toPlain.flush();
|
||||||
|
toPlain.close();
|
||||||
PainlessResult result = decryptionStream.getResult();
|
|
||||||
|
|
||||||
|
PainlessResult result = fromEncrypted.getResult();
|
||||||
return new DecryptedBytesAndMetadata(toPlain.toByteArray(),
|
return new DecryptedBytesAndMetadata(toPlain.toByteArray(),
|
||||||
result.getVerifiedSignatureKeyIds(),
|
result.getVerifiedSignatureKeyIds(),
|
||||||
result.getDecryptionKeyId());
|
result.getDecryptionKeyId());
|
||||||
|
@ -195,7 +278,7 @@ public class PainlessOpenPgpProvider implements OpenPgpProvider {
|
||||||
public byte[] symmetricallyEncryptWithPassword(byte[] bytes, String password)
|
public byte[] symmetricallyEncryptWithPassword(byte[] bytes, String password)
|
||||||
throws SmackOpenPgpException, IOException {
|
throws SmackOpenPgpException, IOException {
|
||||||
try {
|
try {
|
||||||
return PGPainless.encryptWithPassword(bytes, password.toCharArray());
|
return PGPainless.encryptWithPassword(bytes, password.toCharArray(), SymmetricKeyAlgorithm.AES_256);
|
||||||
} catch (PGPException e) {
|
} catch (PGPException e) {
|
||||||
throw new SmackOpenPgpException(e);
|
throw new SmackOpenPgpException(e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -147,13 +147,11 @@ public final class OXInstantMessagingManager extends Manager {
|
||||||
}
|
}
|
||||||
|
|
||||||
OpenPgpProvider provider = openPgpManager.getOpenPgpProvider();
|
OpenPgpProvider provider = openPgpManager.getOpenPgpProvider();
|
||||||
|
byte[] decoded = Base64.decode(element.getEncryptedBase64MessageContent());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
OpenPgpEncryptedChat encryptedChat = chatWith(from);
|
OpenPgpEncryptedChat encryptedChat = chatWith(from);
|
||||||
DecryptedBytesAndMetadata decryptedBytes = provider.decrypt(Base64.decode(
|
DecryptedBytesAndMetadata decryptedBytes = provider.decrypt(decoded, from.asBareJid(), null);
|
||||||
element.getEncryptedBase64MessageContent()),
|
|
||||||
from.asBareJid(),
|
|
||||||
null);
|
|
||||||
|
|
||||||
OpenPgpMessage openPgpMessage = new OpenPgpMessage(decryptedBytes.getBytes(),
|
OpenPgpMessage openPgpMessage = new OpenPgpMessage(decryptedBytes.getBytes(),
|
||||||
new OpenPgpMessage.Metadata(decryptedBytes.getDecryptionKey(),
|
new OpenPgpMessage.Metadata(decryptedBytes.getDecryptionKey(),
|
||||||
|
|
|
@ -22,7 +22,6 @@ import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.jivesoftware.smack.util.Objects;
|
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.CryptElement;
|
||||||
import org.jivesoftware.smackx.ox.element.OpenPgpContentElement;
|
import org.jivesoftware.smackx.ox.element.OpenPgpContentElement;
|
||||||
import org.jivesoftware.smackx.ox.element.OpenPgpElement;
|
import org.jivesoftware.smackx.ox.element.OpenPgpElement;
|
||||||
|
@ -70,7 +69,7 @@ public class OpenPgpMessage {
|
||||||
}
|
}
|
||||||
|
|
||||||
public OpenPgpMessage(byte[] bytes, Metadata metadata) {
|
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();
|
this.state = metadata.getState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue