/*
* Kontalk Android client
* Copyright (C) 2017 Kontalk Devteam <devteam@kontalk.org>
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.kontalk.crypto;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import java.security.SignatureException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import org.spongycastle.bcpg.HashAlgorithmTags;
import org.spongycastle.openpgp.PGPCompressedData;
import org.spongycastle.openpgp.PGPCompressedDataGenerator;
import org.spongycastle.openpgp.PGPEncryptedData;
import org.spongycastle.openpgp.PGPEncryptedDataGenerator;
import org.spongycastle.openpgp.PGPEncryptedDataList;
import org.spongycastle.openpgp.PGPException;
import org.spongycastle.openpgp.PGPLiteralData;
import org.spongycastle.openpgp.PGPLiteralDataGenerator;
import org.spongycastle.openpgp.PGPObjectFactory;
import org.spongycastle.openpgp.PGPOnePassSignature;
import org.spongycastle.openpgp.PGPOnePassSignatureList;
import org.spongycastle.openpgp.PGPPrivateKey;
import org.spongycastle.openpgp.PGPPublicKeyEncryptedData;
import org.spongycastle.openpgp.PGPPublicKeyRing;
import org.spongycastle.openpgp.PGPSignature;
import org.spongycastle.openpgp.PGPSignatureGenerator;
import org.spongycastle.openpgp.PGPSignatureList;
import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator;
import org.spongycastle.openpgp.operator.KeyFingerPrintCalculator;
import org.spongycastle.openpgp.operator.bc.BcPGPContentSignerBuilder;
import org.spongycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider;
import org.spongycastle.openpgp.operator.bc.BcPGPDataEncryptorBuilder;
import org.spongycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory;
import org.spongycastle.openpgp.operator.bc.BcPublicKeyKeyEncryptionMethodGenerator;
import org.kontalk.client.EndpointServer;
import org.kontalk.message.TextComponent;
import org.kontalk.util.CPIMMessage;
import org.kontalk.util.XMPPUtils;
import static org.kontalk.crypto.DecryptException.DECRYPT_EXCEPTION_INTEGRITY_CHECK;
import static org.kontalk.crypto.DecryptException.DECRYPT_EXCEPTION_INVALID_DATA;
import static org.kontalk.crypto.DecryptException.DECRYPT_EXCEPTION_INVALID_RECIPIENT;
import static org.kontalk.crypto.DecryptException.DECRYPT_EXCEPTION_INVALID_SENDER;
import static org.kontalk.crypto.DecryptException.DECRYPT_EXCEPTION_INVALID_TIMESTAMP;
import static org.kontalk.crypto.DecryptException.DECRYPT_EXCEPTION_PRIVATE_KEY_NOT_FOUND;
import static org.kontalk.crypto.DecryptException.DECRYPT_EXCEPTION_VERIFICATION_FAILED;
import static org.kontalk.crypto.VerifyException.VERIFY_EXCEPTION_INVALID_DATA;
import static org.kontalk.crypto.VerifyException.VERIFY_EXCEPTION_VERIFICATION_FAILED;
/**
* PGP coder implementation.
* @author Daniele Ricci
*/
public class PGPCoder extends Coder {
private static final KeyFingerPrintCalculator sFingerprintCalculator =
PGP.sFingerprintCalculator;
/** Buffer size. It should always be a power of 2. */
private static final int BUFFER_SIZE = 1 << 8;
private final EndpointServer mServer;
private final PersonalKey mKey;
// either one of these two has a value
private final PGPPublicKeyRing[] mRecipients;
private final PGPPublicKeyRing mSender;
public PGPCoder(EndpointServer server, PersonalKey key, PGPPublicKeyRing[] recipients) {
mServer = server;
mKey = key;
mRecipients = recipients;
mSender = null;
}
public PGPCoder(EndpointServer server, PersonalKey key, PGPPublicKeyRing sender) {
mServer = server;
mKey = key;
mRecipients = null;
mSender = sender;
}
@Override
public byte[] encryptText(CharSequence text) throws GeneralSecurityException {
try {
// consider plain text
return encryptData("text/plain", text);
}
catch (PGPException e) {
throw new GeneralSecurityException(e);
}
catch (IOException e) {
throw new GeneralSecurityException(e);
}
}
@Override
public byte[] encryptStanza(CharSequence xml) throws GeneralSecurityException {
try {
// prepare XML wrapper
final String xmlWrapper =
"<xmpp xmlns='jabber:client'>" +
xml +
"</xmpp>";
return encryptData(XMPPUtils.XML_XMPP_TYPE, xmlWrapper);
}
catch (PGPException e) {
throw new GeneralSecurityException(e);
}
catch (IOException e) {
throw new GeneralSecurityException(e);
}
}
private byte[] encryptData(String mime, CharSequence data)
throws PGPException, IOException, SignatureException {
String from = mKey.getUserId(mServer.getNetwork());
String[] to = new String[mRecipients.length];
for (int i = 0; i < to.length; i++)
to[i] = PGP.getUserId(PGP.getMasterKey(mRecipients[i]), mServer.getNetwork());
// secure the message against the most basic attacks using Message/CPIM
CPIMMessage cpim = new CPIMMessage(from, to, new Date(), mime, data);
byte[] plainText = cpim.toByteArray();
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayInputStream in = new ByteArrayInputStream(plainText);
// setup data encryptor & generator
BcPGPDataEncryptorBuilder encryptor = new BcPGPDataEncryptorBuilder(PGPEncryptedData.AES_192);
encryptor.setWithIntegrityPacket(true);
encryptor.setSecureRandom(new SecureRandom());
// add public key recipients
PGPEncryptedDataGenerator encGen = new PGPEncryptedDataGenerator(encryptor);
for (PGPPublicKeyRing rcpt : mRecipients)
encGen.addMethod(new BcPublicKeyKeyEncryptionMethodGenerator(PGP.getEncryptionKey(rcpt)));
OutputStream encryptedOut = encGen.open(out, new byte[BUFFER_SIZE]);
// setup compressed data generator
PGPCompressedDataGenerator compGen = new PGPCompressedDataGenerator(PGPCompressedData.ZIP);
OutputStream compressedOut = compGen.open(encryptedOut, new byte[BUFFER_SIZE]);
// setup signature generator
PGPSignatureGenerator sigGen = new PGPSignatureGenerator
(new BcPGPContentSignerBuilder(mKey.getSignKeyPair()
.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA256));
sigGen.init(PGPSignature.BINARY_DOCUMENT, mKey.getSignKeyPair().getPrivateKey());
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
spGen.setSignerUserID(false, mKey.getUserId(mServer.getNetwork()));
sigGen.setUnhashedSubpackets(spGen.generate());
sigGen.generateOnePassVersion(false)
.encode(compressedOut);
// Initialize literal data generator
PGPLiteralDataGenerator literalGen = new PGPLiteralDataGenerator();
OutputStream literalOut = literalGen.open(
compressedOut,
PGPLiteralData.BINARY,
"",
new Date(),
new byte[BUFFER_SIZE]);
// read the "in" stream, compress, encrypt and write to the "out" stream
// this must be done if clear data is bigger than the buffer size
// but there are other ways to optimize...
byte[] buf = new byte[BUFFER_SIZE];
int len;
while ((len = in.read(buf)) > 0) {
literalOut.write(buf, 0, len);
sigGen.update(buf, 0, len);
}
in.close();
literalGen.close();
// Generate the signature, compress, encrypt and write to the "out" stream
sigGen.generate().encode(compressedOut);
compGen.close();
encGen.close();
return out.toByteArray();
}
@SuppressWarnings("unchecked")
@Override
public DecryptOutput decryptText(byte[] encrypted, boolean verify)
throws GeneralSecurityException {
List<DecryptException> errors = new ArrayList<>();
String mime = null;
Date timestamp = null;
String out = null;
try {
PGPObjectFactory pgpF = new PGPObjectFactory(encrypted, sFingerprintCalculator);
PGPEncryptedDataList enc;
Object o = pgpF.nextObject();
// the first object might be a PGP marker packet
if (o instanceof PGPEncryptedDataList) {
enc = (PGPEncryptedDataList) o;
}
else {
enc = (PGPEncryptedDataList) pgpF.nextObject();
}
// check if secret key matches
Iterator<PGPPublicKeyEncryptedData> it = enc.getEncryptedDataObjects();
PGPPrivateKey sKey = null;
PGPPublicKeyEncryptedData pbe = null;
// our encryption keyID
long ourKeyID = mKey.getEncryptKeyPair().getPrivateKey().getKeyID();
while (sKey == null && it.hasNext()) {
pbe = it.next();
if (pbe.getKeyID() == ourKeyID)
sKey = mKey.getEncryptKeyPair().getPrivateKey();
}
if (sKey == null) {
// unrecoverable situation
throw new DecryptException(
DECRYPT_EXCEPTION_PRIVATE_KEY_NOT_FOUND,
"Secret key for message not found.");
}
InputStream clear = pbe.getDataStream(new BcPublicKeyDataDecryptorFactory(sKey));
PGPObjectFactory plainFact = new PGPObjectFactory(clear, sFingerprintCalculator);
Object message = plainFact.nextObject();
CharSequence msgData = null;
if (message instanceof PGPCompressedData) {
PGPCompressedData cData = (PGPCompressedData) message;
PGPObjectFactory pgpFact = new PGPObjectFactory(cData.getDataStream(), sFingerprintCalculator);
message = pgpFact.nextObject();
PGPOnePassSignature ops = null;
if (message instanceof PGPOnePassSignatureList) {
if (verify && mSender != null) {
ops = ((PGPOnePassSignatureList) message).get(0);
try {
ops.init(new BcPGPContentVerifierBuilderProvider(), PGP.getSigningKey(mSender));
}
catch (ClassCastException e) {
try {
// workaround for backward compatibility
ops.init(new BcPGPContentVerifierBuilderProvider(), PGP.getMasterKey(mSender));
}
catch (ClassCastException e2) {
// peer used new ECC key to sign, but we still have the old RSA one
// no verification is possible
ops = null;
}
}
}
message = pgpFact.nextObject();
}
if (message instanceof PGPLiteralData) {
PGPLiteralData ld = (PGPLiteralData) message;
InputStream unc = ld.getInputStream();
ByteArrayOutputStream bout = new ByteArrayOutputStream();
byte[] buf = new byte[4096];
int num;
while ((num = unc.read(buf)) >= 0) {
bout.write(buf, 0, num);
if (ops != null)
ops.update(buf, 0, num);
}
if (verify) {
if (ops == null) {
errors.add(new DecryptException(
DECRYPT_EXCEPTION_VERIFICATION_FAILED,
"No signature list found"));
}
message = pgpFact.nextObject();
if (ops != null) {
if (message instanceof PGPSignatureList) {
PGPSignature signature = ((PGPSignatureList) message).get(0);
if (!ops.verify(signature)) {
errors.add(new DecryptException(
DECRYPT_EXCEPTION_VERIFICATION_FAILED,
"Signature verification failed"));
}
}
else {
errors.add(new DecryptException(
DECRYPT_EXCEPTION_INVALID_DATA,
"Invalid signature packet"));
}
}
}
// verify message integrity
if (pbe.isIntegrityProtected()) {
try {
if (!pbe.verify()) {
// unrecoverable situation
throw new DecryptException(
DECRYPT_EXCEPTION_INTEGRITY_CHECK,
"Message integrity check failed");
}
}
catch (PGPException e) {
// unrecoverable situation
throw new DecryptException(
DECRYPT_EXCEPTION_INTEGRITY_CHECK,
e);
}
}
String data = bout.toString();
try {
// parse and check Message/CPIM
CPIMMessage msg = CPIMMessage.parse(data);
mime = msg.getMime();
msgData = msg.getBody();
if (verify) {
// verify CPIM headers, including mime type must be either text or xml
// check mime type
if (!TextComponent.MIME_TYPE.equalsIgnoreCase(msg.getMime()) &&
!XMPPUtils.XML_XMPP_TYPE.equalsIgnoreCase(msg.getMime())) {
// unrecoverable situation
throw new DecryptException(
DECRYPT_EXCEPTION_INTEGRITY_CHECK,
"MIME type mismatch");
}
// check that the recipient matches the full uid of the personal key
boolean foundTo = false;
String myUid = mKey.getUserId(mServer.getNetwork());
String[] msgTo = msg.getTo();
if (msgTo != null) {
for (String to : msgTo) {
if (myUid.equals(to)) {
foundTo = true;
break;
}
}
}
if (!foundTo) {
errors.add(new DecryptException(
DECRYPT_EXCEPTION_INVALID_RECIPIENT,
"Destination does not match personal key"));
}
// check that the sender matches the full uid of the sender's key
if (mSender != null) {
String otherUid = PGP.getUserId(PGP.getMasterKey(mSender), mServer.getNetwork());
if (!otherUid.equals(msg.getFrom())) {
errors.add(new DecryptException(
DECRYPT_EXCEPTION_INVALID_SENDER,
"Sender does not match sender's key"));
}
}
else {
errors.add(new DecryptException(
DECRYPT_EXCEPTION_VERIFICATION_FAILED,
"No public key available to verify sender"));
}
timestamp = msg.getDate();
if (timestamp == null) {
errors.add(new DecryptException(
DECRYPT_EXCEPTION_INVALID_TIMESTAMP,
"Invalid timestamp"));
}
// check DateTime (plain text only, <delay/> is left to the caller)
if (TextComponent.MIME_TYPE.equalsIgnoreCase(msg.getMime())) {
long time = msg.getDate().getTime();
long now = System.currentTimeMillis();
long diff = Math.abs(now - time);
if (diff > TIMEDIFF_THRESHOLD) {
errors.add(new DecryptException(
DECRYPT_EXCEPTION_INVALID_TIMESTAMP,
"Drifted timestamp"));
}
}
}
}
catch (ParseException pe) {
// return data as-is
msgData = data;
if (verify) {
// verification requested: invalid CPIM data
errors.add(new DecryptException(
DECRYPT_EXCEPTION_INVALID_DATA, pe,
"Verification was requested but no CPIM valid data was found"));
}
}
catch (DecryptException de) {
errors.add(de);
}
finally {
if (msgData != null)
out = String.valueOf(msgData);
}
}
else {
// invalid or unknown packet
throw new DecryptException(
DECRYPT_EXCEPTION_INVALID_DATA,
"Unknown packet type " + message.getClass().getName());
}
}
else {
throw new DecryptException(DecryptException
.DECRYPT_EXCEPTION_INVALID_DATA,
"Compressed data packet expected");
}
}
// unrecoverable situations
catch (IOException ioe) {
throw new DecryptException(DECRYPT_EXCEPTION_INVALID_DATA, ioe);
}
catch (PGPException pe) {
throw new DecryptException(DECRYPT_EXCEPTION_INVALID_DATA, pe);
}
return new DecryptOutput(out, mime, timestamp, errors);
}
@Override
public void encryptFile(InputStream input, OutputStream output) throws GeneralSecurityException {
try {
// setup data encryptor & generator
BcPGPDataEncryptorBuilder encryptor = new BcPGPDataEncryptorBuilder(PGPEncryptedData.AES_192);
encryptor.setWithIntegrityPacket(true);
encryptor.setSecureRandom(new SecureRandom());
// add public key recipients
PGPEncryptedDataGenerator encGen = new PGPEncryptedDataGenerator(encryptor);
for (PGPPublicKeyRing rcpt : mRecipients)
encGen.addMethod(new BcPublicKeyKeyEncryptionMethodGenerator(PGP.getEncryptionKey(rcpt)));
OutputStream encryptedOut = encGen.open(output, new byte[BUFFER_SIZE]);
// setup compressed data generator
PGPCompressedDataGenerator compGen = new PGPCompressedDataGenerator(PGPCompressedData.ZIP);
OutputStream compressedOut = compGen.open(encryptedOut, new byte[BUFFER_SIZE]);
// setup signature generator
PGPSignatureGenerator sigGen = new PGPSignatureGenerator
(new BcPGPContentSignerBuilder(mKey.getSignKeyPair()
.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA256));
sigGen.init(PGPSignature.BINARY_DOCUMENT, mKey.getSignKeyPair().getPrivateKey());
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
spGen.setSignerUserID(false, mKey.getUserId(mServer.getNetwork()));
sigGen.setUnhashedSubpackets(spGen.generate());
sigGen.generateOnePassVersion(false)
.encode(compressedOut);
// Initialize literal data generator
PGPLiteralDataGenerator literalGen = new PGPLiteralDataGenerator();
OutputStream literalOut = literalGen.open(
compressedOut,
PGPLiteralData.BINARY,
"",
new Date(),
new byte[BUFFER_SIZE]);
// read the "in" stream, compress, encrypt and write to the "out" stream
// this must be done if clear data is bigger than the buffer size
// but there are other ways to optimize...
byte[] buf = new byte[BUFFER_SIZE];
int len;
while ((len = input.read(buf)) > 0) {
literalOut.write(buf, 0, len);
sigGen.update(buf, 0, len);
}
literalGen.close();
// Generate the signature, compress, encrypt and write to the "out" stream
sigGen.generate().encode(compressedOut);
compGen.close();
encGen.close();
}
catch (PGPException e) {
throw new GeneralSecurityException(e);
}
catch (IOException e) {
throw new GeneralSecurityException(e);
}
}
/** Decrypts a file. */
@SuppressWarnings("unchecked")
public void decryptFile(InputStream input, boolean verify,
OutputStream output, List<DecryptException> errors)
throws GeneralSecurityException {
try {
PGPObjectFactory pgpF = new PGPObjectFactory(input, sFingerprintCalculator);
PGPEncryptedDataList enc;
Object o = pgpF.nextObject();
// the first object might be a PGP marker packet
if (o instanceof PGPEncryptedDataList) {
enc = (PGPEncryptedDataList) o;
}
else {
enc = (PGPEncryptedDataList) pgpF.nextObject();
}
// check if secret key matches
Iterator<PGPPublicKeyEncryptedData> it = enc.getEncryptedDataObjects();
PGPPrivateKey sKey = null;
PGPPublicKeyEncryptedData pbe = null;
// our encryption keyID
long ourKeyID = mKey.getEncryptKeyPair().getPrivateKey().getKeyID();
while (sKey == null && it.hasNext()) {
pbe = it.next();
if (pbe.getKeyID() == ourKeyID)
sKey = mKey.getEncryptKeyPair().getPrivateKey();
}
if (sKey == null)
throw new DecryptException(
DECRYPT_EXCEPTION_PRIVATE_KEY_NOT_FOUND,
"Secret key for message not found.");
InputStream clear = pbe.getDataStream(new BcPublicKeyDataDecryptorFactory(sKey));
PGPObjectFactory plainFact = new PGPObjectFactory(clear, sFingerprintCalculator);
Object message = plainFact.nextObject();
if (message instanceof PGPCompressedData) {
PGPCompressedData cData = (PGPCompressedData) message;
PGPObjectFactory pgpFact = new PGPObjectFactory(cData.getDataStream(), sFingerprintCalculator);
message = pgpFact.nextObject();
PGPOnePassSignature ops = null;
if (message instanceof PGPOnePassSignatureList) {
if (verify && mSender != null) {
ops = ((PGPOnePassSignatureList) message).get(0);
ops.init(new BcPGPContentVerifierBuilderProvider(), PGP.getSigningKey(mSender));
}
message = pgpFact.nextObject();
}
if (message instanceof PGPLiteralData) {
PGPLiteralData ld = (PGPLiteralData) message;
InputStream unc = ld.getInputStream();
byte[] buf = new byte[8192];
int num;
while ((num = unc.read(buf)) >= 0) {
output.write(buf, 0, num);
if (ops != null)
ops.update(buf, 0, num);
}
if (verify) {
if (ops == null) {
if (errors != null)
errors.add(new DecryptException(
DECRYPT_EXCEPTION_VERIFICATION_FAILED,
"No signature list found"));
}
message = pgpFact.nextObject();
if (ops != null) {
if (message instanceof PGPSignatureList) {
PGPSignature signature = ((PGPSignatureList) message).get(0);
if (!ops.verify(signature)) {
if (errors != null)
errors.add(new DecryptException(
DECRYPT_EXCEPTION_VERIFICATION_FAILED,
"Signature verification failed"));
}
}
else {
if (errors != null)
errors.add(new DecryptException(
DECRYPT_EXCEPTION_INVALID_DATA,
"Invalid signature packet"));
}
}
}
// verify message integrity
if (pbe.isIntegrityProtected()) {
try {
if (!pbe.verify()) {
// unrecoverable situation
throw new DecryptException(
DECRYPT_EXCEPTION_INTEGRITY_CHECK,
"Message integrity check failed");
}
}
catch (PGPException e) {
// unrecoverable situation
throw new DecryptException(
DECRYPT_EXCEPTION_INTEGRITY_CHECK,
e);
}
}
}
else {
// invalid or unknown packet
throw new DecryptException(
DECRYPT_EXCEPTION_INVALID_DATA,
"Unknown packet type " + message.getClass().getName());
}
}
else {
throw new DecryptException(DecryptException
.DECRYPT_EXCEPTION_INVALID_DATA,
"Compressed data packet expected");
}
}
// unrecoverable situations
catch (IOException ioe) {
throw new DecryptException(DECRYPT_EXCEPTION_INVALID_DATA, ioe);
}
catch (PGPException pe) {
throw new DecryptException(DECRYPT_EXCEPTION_INVALID_DATA, pe);
}
}
@Override
public VerifyOutput verifyText(byte[] signed, boolean verify) throws GeneralSecurityException {
List<VerifyException> errors = new ArrayList<>();
Date timestamp;
String out;
try {
PGPObjectFactory plainFact = new PGPObjectFactory(signed, sFingerprintCalculator);
Object message = plainFact.nextObject();
if (message instanceof PGPCompressedData) {
PGPCompressedData cData = (PGPCompressedData) message;
PGPObjectFactory pgpFact = new PGPObjectFactory(cData.getDataStream(), sFingerprintCalculator);
message = pgpFact.nextObject();
PGPOnePassSignature ops = null;
if (message instanceof PGPOnePassSignatureList) {
if (verify && mSender != null) {
ops = ((PGPOnePassSignatureList) message).get(0);
try {
ops.init(new BcPGPContentVerifierBuilderProvider(), PGP.getSigningKey(mSender));
}
catch (ClassCastException e) {
try {
// workaround for backward compatibility
ops.init(new BcPGPContentVerifierBuilderProvider(), PGP.getMasterKey(mSender));
}
catch (ClassCastException e2) {
// peer used new ECC key to sign, but we still have the old RSA one
// no verification is possible
ops = null;
}
}
}
message = pgpFact.nextObject();
}
if (message instanceof PGPLiteralData) {
PGPLiteralData ld = (PGPLiteralData) message;
timestamp = ld.getModificationTime();
InputStream unc = ld.getInputStream();
ByteArrayOutputStream bout = new ByteArrayOutputStream();
byte[] buf = new byte[4096];
int num;
while ((num = unc.read(buf)) >= 0) {
bout.write(buf, 0, num);
if (ops != null)
ops.update(buf, 0, num);
}
if (verify) {
if (ops == null) {
errors.add(new VerifyException(
VERIFY_EXCEPTION_VERIFICATION_FAILED,
"No signature list found"));
}
message = pgpFact.nextObject();
if (ops != null) {
if (message instanceof PGPSignatureList) {
PGPSignature signature = ((PGPSignatureList) message).get(0);
if (!ops.verify(signature)) {
errors.add(new VerifyException(
VERIFY_EXCEPTION_VERIFICATION_FAILED,
"Signature verification failed"));
}
}
else {
errors.add(new VerifyException(
VERIFY_EXCEPTION_INVALID_DATA,
"Invalid signature packet"));
}
}
}
out = bout.toString();
}
else {
// invalid or unknown packet
throw new VerifyException(
VERIFY_EXCEPTION_INVALID_DATA,
"Unknown packet type " + message.getClass().getName());
}
}
else {
throw new VerifyException(
VERIFY_EXCEPTION_INVALID_DATA,
"Compressed data packet expected");
}
}
// unrecoverable situations
catch (IOException ioe) {
throw new VerifyException(VERIFY_EXCEPTION_INVALID_DATA, ioe);
}
catch (PGPException pe) {
throw new VerifyException(VERIFY_EXCEPTION_INVALID_DATA, pe);
}
return new VerifyOutput(out, timestamp, errors);
}
}