diff --git a/src/main/java/org/c02e/jpgpj/Decryptor.java b/src/main/java/org/c02e/jpgpj/Decryptor.java index 7f28fba..801355d 100644 --- a/src/main/java/org/c02e/jpgpj/Decryptor.java +++ b/src/main/java/org/c02e/jpgpj/Decryptor.java @@ -77,6 +77,7 @@ public class Decryptor { public static final int DEFAULT_MAX_FILE_BUFFER_SIZE = 0x100000; // 1MB public static final boolean DEFAULT_VERIFICATION_REQUIRED = true; public static final int DEFAULT_COPY_FILE_BUFFER_SIZE = 0x4000; + public static final boolean DEFAULT_LOGGING_ENABLED = false; protected boolean verificationRequired = DEFAULT_VERIFICATION_REQUIRED; protected char[] symmetricPassphraseChars; @@ -85,6 +86,7 @@ public class Decryptor { protected String symmetricPassphrase; protected int maxFileBufferSize = DEFAULT_MAX_FILE_BUFFER_SIZE; //1MB protected int copyFileBufferSize = DEFAULT_COPY_FILE_BUFFER_SIZE; + protected boolean loggingEnabled = DEFAULT_LOGGING_ENABLED; protected Ring ring; protected final Logger log = LoggerFactory.getLogger(Decryptor.class.getName()); @@ -254,6 +256,32 @@ public Decryptor withRing(Ring x) { return this; } + /** + * @return {@code true} if logging a brief summary of the execution + * every time decryption is executed (e.g. file name/path, size, compression + * type, etc.). Note: errors/warnings logging are not affected by + * this setting + */ + public boolean isLoggingEnabled() { + return loggingEnabled; + } + + /** + * @param enabled {@code true} if should log a brief summary of the execution + * every time decryption is executed (e.g. file name/path, size, compression + * type, etc.). Note: errors/warnings logging are not affected by + * this setting + */ + public void setLoggingEnabled(boolean enabled) { + loggingEnabled = enabled; + } + + /** @see #setLoggingEnabled(boolean) */ + public Decryptor withLoggingEnabled(boolean enabled) { + setLoggingEnabled(enabled); + return this; + } + /** * Zeroes-out the cached passphrase for all keys, * and releases the extracted private key material for garbage collection. @@ -328,7 +356,9 @@ public FileMetadata decrypt(Path ciphertext, Path plaintext) // delete old output file if (Files.deleteIfExists(plaintext)) { - log.debug("decrypt({}) deleted {}", ciphertext, plaintext); + if (isLoggingEnabled()) { + log.debug("decrypt({}) deleted {}", ciphertext, plaintext); + } } long inputSize = Files.size(ciphertext); @@ -341,7 +371,9 @@ public FileMetadata decrypt(Path ciphertext, Path plaintext) // delete output file if anything went wrong try { if (Files.deleteIfExists(plaintext)) { - log.debug("decrypt({}) cleaned up {}", ciphertext, plaintext); + if (isLoggingEnabled()) { + log.debug("decrypt({}) cleaned up {}", ciphertext, plaintext); + } } } catch (IOException ioe) { log.warn("decrypt({}) cannot clean up {}", ciphertext, plaintext, ioe); @@ -458,10 +490,13 @@ protected List unpack(Iterator packets, OutputStream plaintext) List meta = new ArrayList(); List verifiers = new ArrayList(); + boolean logUnpacking = isLoggingEnabled(); while (packets.hasNext()) { Object packet = packets.next(); - log.trace("unpack {}", packet.getClass()); + if (logUnpacking) { + log.trace("unpack {}", packet.getClass()); + } if (packet instanceof PGPMarker) { // no-op @@ -500,7 +535,10 @@ protected List unpack(Iterator packets, OutputStream plaintext) throw new PGPException("unexpected packet: " + packet.getClass()); } } - log.trace("unpacked all"); + + if (logUnpacking) { + log.trace("unpacked all"); + } // fail if verification required and any signature is bad verify(verifiers, meta); @@ -550,6 +588,7 @@ protected InputStream decrypt(Iterator data) PGPPBEEncryptedData pbe = null; Ring decryptRing = getRing(); + boolean logDecryption = isLoggingEnabled(); while (data.hasNext()) { Object o = data.next(); @@ -565,11 +604,15 @@ protected InputStream decrypt(Iterator data) if (isUsableForDecryption(subkey)) return decrypt(pke, subkey); - log.info("not using decryption key {}", subkey); + if (logDecryption) { + log.info("not using decryption key {}", subkey); + } } if (Util.isEmpty(keys)) - log.info("not found decryption key {}", Util.formatKeyId(id)); + if (logDecryption) { + log.info("not found decryption key {}", Util.formatKeyId(id)); + } } else if (o instanceof PGPPBEEncryptedData) { // try first symmetric-key option at the end @@ -588,8 +631,9 @@ protected InputStream decrypt(PGPPublicKeyEncryptedData data, Subkey subkey) throws IOException, PGPException { if (data == null || subkey == null) throw new DecryptionException("no suitable decryption key found"); - - log.info("using decryption key {}", subkey); + if (isLoggingEnabled()) { + log.info("using decryption key {}", subkey); + } return data.getDataStream(buildPublicKeyDecryptor(subkey)); } @@ -658,8 +702,9 @@ protected void verify(List verifiers, List meta) if (!verifier.verify()) throw new VerificationException( "bad signature for key " + verifier.key); - else + if (isLoggingEnabled()) { log.debug("good signature for key {}", verifier.key); + } Key key = verifier.getSignedBy(); for (FileMetadata file : meta) { @@ -818,24 +863,31 @@ public Key getSignedBy() throws PGPException { * Finds verification subkey by ID in this Decryptor's ring, or null. * If found, also sets "key" field to subkey's key. */ - private Subkey findVerificationSubkey(Long id) { + protected Subkey findVerificationSubkey(Long id) { Ring decryptorRing = getRing(); List keys = decryptorRing.findAll(id); - + boolean logVerification = isLoggingEnabled(); for (Key key: keys) { Subkey subkey = key.findById(id); if (subkey != null && subkey.isForVerification()) { - log.info("using verification key {}", subkey); + if (logVerification) { + log.info("using verification key {} for ID={}", subkey, Util.formatKeyId(id)); + } this.key = key; return subkey; } - log.info("not using verification key {}", subkey); + if (logVerification) { + log.info("not using verification key {} for ID={}", subkey, Util.formatKeyId(id)); + } } - if (Util.isEmpty(keys)) - log.info("not found verification key {}", Util.formatKeyId(id)); + if (Util.isEmpty(keys)) { + if (logVerification) { + log.info("not found verification key {}", Util.formatKeyId(id)); + } + } return null; } diff --git a/src/main/java/org/c02e/jpgpj/Encryptor.java b/src/main/java/org/c02e/jpgpj/Encryptor.java index caa7ff3..741b330 100644 --- a/src/main/java/org/c02e/jpgpj/Encryptor.java +++ b/src/main/java/org/c02e/jpgpj/Encryptor.java @@ -2,8 +2,8 @@ import java.io.BufferedInputStream; import java.io.BufferedOutputStream; +import java.io.ByteArrayInputStream; import java.io.File; -import java.io.FileOutputStream; import java.io.FilterOutputStream; import java.io.IOException; import java.io.InputStream; @@ -90,6 +90,7 @@ public class Encryptor { public static final int DEFAULT_KEY_DERIVATION_ALGORITHM_WORK_FACTOR = 255; public static final int DEFAULT_MAX_FILE_BUFFER_SIZE = 0x100000; // 1MB + public static final boolean DEFAULT_LOGGING_ENABLED = false; protected boolean asciiArmored = DEFAULT_ASCII_ARMORED; protected boolean removeDefaultArmoredVersionHeader = DEFAULT_REMOVE_DEFAULT_ARMORED_VERSION_HEADER; @@ -109,6 +110,7 @@ public class Encryptor { protected int keyDerivationWorkFactor = DEFAULT_KEY_DERIVATION_ALGORITHM_WORK_FACTOR; protected int maxFileBufferSize = DEFAULT_MAX_FILE_BUFFER_SIZE; + protected boolean loggingEnabled = DEFAULT_LOGGING_ENABLED; protected Ring ring; protected final Logger log = LoggerFactory.getLogger(Encryptor.class.getName()); @@ -568,6 +570,32 @@ public Encryptor withRing(Ring x) { return this; } + /** + * @return {@code true} if logging a brief summary of the execution + * every time encryption is executed (e.g. file name/path, size, compression + * type, etc.). Note: errors/warnings logging are not affected by + * this setting + */ + public boolean isLoggingEnabled() { + return loggingEnabled; + } + + /** + * @param enabled {@code true} if should log a brief summary of the execution + * every time encryption is executed (e.g. file name/path, size, compression + * type, etc.). Note: errors/warnings logging are not affected by + * this setting + */ + public void setLoggingEnabled(boolean enabled) { + loggingEnabled = enabled; + } + + /** @see #setLoggingEnabled(boolean) */ + public Encryptor withLoggingEnabled(boolean enabled) { + setLoggingEnabled(enabled); + return this; + } + /** * Zeroes-out the cached passphrase for all keys, * and releases the extracted private key material for garbage collection. @@ -636,7 +664,9 @@ public FileMetadata encrypt(Path plaintext, Path ciphertext) // delete old output file if (Files.deleteIfExists(ciphertext)) { - log.debug("encrypt({}) deleted {}", plaintext, ciphertext); + if (isLoggingEnabled()) { + log.debug("encrypt({}) deleted {}", plaintext, ciphertext); + } } FileMetadata meta = new FileMetadata(plaintext); @@ -650,7 +680,9 @@ public FileMetadata encrypt(Path plaintext, Path ciphertext) // delete output file if anything went wrong try { if (Files.deleteIfExists(ciphertext)) { - log.debug("encrypt({}) cleaned up {}", plaintext, ciphertext); + if (isLoggingEnabled()) { + log.debug("encrypt({}) cleaned up {}", plaintext, ciphertext); + } } } catch(IOException ioe) { log.warn("encrypt({}) cannot clean up {}", plaintext, ciphertext, ioe); @@ -676,7 +708,7 @@ public InputStream wrapSourceInputStream(InputStream sourceStream, long inputSiz * @param targetStream Original target (ciphertext) {@link OutputStream} * @param inputSize Expected input (plaintext) size * @return A wrapper buffered stream optimized for the input size according to - * the current encryptor settings + * the current encryptor settings. * @throws IOException If failed to generate the wrapper * @see #estimateOutFileBufferSize(long) */ @@ -685,6 +717,90 @@ public OutputStream wrapTargetOutputStream(OutputStream targetStream, long input return new BufferedOutputStream(targetStream, bestFileBufferSize); } + /** + * @param data Data buffer to be used as plaintext input + * @param name The "file" name to report as being encrypted - can be {@code null} + * @param ciphertext Target ciphertext {@link File} + * @return The {@link FileMetadata} of the encrypted plaintext + * @throws IOException if an IO error occurs reading from or writing to + * the underlying input or output streams. + * @throws PGPException if no encryption keys and no passphrase for + * symmetric encryption were supplied (and the message is not unencrypted), + * or if no signing keys were supplied (and the message is not unsigned). + * @throws PassphraseException if an incorrect passphrase was supplied + * for one of the signing keys. + */ + public FileMetadata encryptBytes(byte[] data, String name, File ciphertext) + throws IOException, PGPException { + return encryptBytes(data, name, ciphertext.toPath()); + } + + /** + * @param data Data buffer to be used as plaintext input + * @param name The "file" name to report as being encrypted - can be {@code null} + * @param ciphertext Target ciphertext {@link Path} + * @return The {@link FileMetadata} of the encrypted plaintext + * @throws IOException if an IO error occurs reading from or writing to + * the underlying input or output streams. + * @throws PGPException if no encryption keys and no passphrase for + * symmetric encryption were supplied (and the message is not unencrypted), + * or if no signing keys were supplied (and the message is not unsigned). + * @throws PassphraseException if an incorrect passphrase was supplied + * for one of the signing keys. + */ + public FileMetadata encryptBytes(byte[] data, String name, Path ciphertext) + throws IOException, PGPException { + // delete old output file + if (Files.deleteIfExists(ciphertext)) { + if (isLoggingEnabled()) { + log.debug("encryptBytes({}) deleted {}", name, ciphertext); + } + } + + try (OutputStream targetStream = Files.newOutputStream(ciphertext); + OutputStream output = wrapTargetOutputStream(targetStream, data.length)) { + return encryptBytes(data, name, output); + } catch (Exception e) { + // delete output file if anything went wrong + try { + if (Files.deleteIfExists(ciphertext)) { + if (isLoggingEnabled()) { + log.debug("encryptBytes({}) cleaned up {}", name, ciphertext); + } + } + } catch(IOException ioe) { + log.warn("encryptBytes({}) cannot clean up {}", name, ciphertext, ioe); + } + + throw e; + } + } + + /** + * @param data Data buffer to be used as plaintext input + * @param name The "file" name to report as being encrypted - can be {@code null} + * @param ciphertext Target ciphertext {@link OutputStream} + * @return The {@link FileMetadata} of the encrypted plaintext + * @throws IOException if an IO error occurs reading from or writing to + * the underlying input or output streams. + * @throws PGPException if no encryption keys and no passphrase for + * symmetric encryption were supplied (and the message is not unencrypted), + * or if no signing keys were supplied (and the message is not unsigned). + * @throws PassphraseException if an incorrect passphrase was supplied + * for one of the signing keys. + */ + public FileMetadata encryptBytes(byte[] data, String name, OutputStream ciphertext) + throws IOException, PGPException { + FileMetadata meta = new FileMetadata(); + meta.setName(name); + meta.setLength(data.length); + meta.setLastModified(System.currentTimeMillis()); + + try (InputStream input = new ByteArrayInputStream(data)) { + return encrypt(input, ciphertext, meta); + } + } + /** * Signs, compresses, and encrypts the specified content as a PGP message * into the specified output stream (with no optional metadata). @@ -766,21 +882,52 @@ public FileMetadata encrypt( */ public OutputStream prepareCiphertextOutputStream(FileMetadata plainMeta, File ciphertext) throws IOException, PGPException { + return prepareCiphertextOutputStream(plainMeta, ciphertext.toPath()); + } + + /** + * Builds a wrapper {@link OutputStream} where everything written to the it is + * encrypted+compressed+signed according to the encryptor's configuration, + * and then written to the specified target file. Closing the wrapper stream finalizes + * the encryption and signature, and finishes writing all the wrapper stream's + * content to the original stream as well as closing the file stream. + * + * @param plainMeta The {@link FileMetadata} describing the plaintext file - if + * {@code null} an empty ad-hoc instance will be created + * @param ciphertext The target {@link Path} for the encrypted data + * @return The wrapper stream + * @throws IOException If failed to wrap the stream + * @throws PGPException If failed to apply a PGP wrapper + */ + public OutputStream prepareCiphertextOutputStream(FileMetadata plainMeta, Path ciphertext) + throws IOException, PGPException { // delete old output file - if (ciphertext.delete()) { - log.debug("prepareCiphertextOutputStream - deleted {}", ciphertext); + if (Files.deleteIfExists(ciphertext)) { + if (isLoggingEnabled()) { + log.debug("prepareCiphertextOutputStream({}) - deleted {}", + (plainMeta == null) ? null : plainMeta.getName(), ciphertext); + } } OutputStream fileStream = null; try { - fileStream = new FileOutputStream(ciphertext); + fileStream = Files.newOutputStream(ciphertext); OutputStream wrapper = prepareCiphertextOutputStream(fileStream, plainMeta, true); fileStream = null; // avoid auto-close at finally clause return wrapper; } catch(Exception e) { // delete output file if anything went wrong if (fileStream != null) { - ciphertext.delete(); + String fileName = (plainMeta == null) ? null : plainMeta.getName(); + try { + if (!Files.deleteIfExists(ciphertext)) { + if (isLoggingEnabled()) { + log.debug("prepareCiphertextOutputStream({}) - cleaned up output file {}", fileName, ciphertext); + } + } + } catch (IOException ioe) { + log.warn(fileName + ": Failed to clean up output file " + ciphertext, ioe); + } } throw e; } finally { @@ -959,7 +1106,10 @@ protected OutputStream armor(OutputStream out, FileMetadata meta) { protected OutputStream encrypt(OutputStream out, FileMetadata meta) throws IOException, PGPException { EncryptionAlgorithm encAlgo = getEncryptionAlgorithm(); - log.trace("using encryption algorithm {}", encAlgo); + if (isLoggingEnabled()) { + log.trace("{}: using encryption algorithm {}", + (meta == null) ? null : meta.getName(), encAlgo); + } if (encAlgo == EncryptionAlgorithm.Unencrypted) return null; @@ -972,10 +1122,10 @@ protected OutputStream encrypt(OutputStream out, FileMetadata meta) PGPEncryptedDataGenerator generator = buildEncryptor(); for (Key key : keys) - generator.addMethod(buildPublicKeyEncryptor(key)); + generator.addMethod(buildPublicKeyEncryptor(key, meta)); if (!Util.isEmpty(passChars)) - generator.addMethod(buildSymmetricKeyEncryptor()); + generator.addMethod(buildSymmetricKeyEncryptor(meta)); return generator.open(out, getEncryptionBuffer(meta)); } @@ -987,7 +1137,10 @@ protected OutputStream compress(OutputStream out, FileMetadata meta) throws IOException, PGPException { CompressionAlgorithm compAlgo = getCompressionAlgorithm(); int compLevel = getCompressionLevel(); - log.trace("using compression algorithm {} - {}", compAlgo, compLevel); + if (isLoggingEnabled()) { + log.trace("{}: using compression algorithm {} - {}", + (meta == null) ? null : meta.getName(), compAlgo, compLevel); + } if (compAlgo == CompressionAlgorithm.Uncompressed || compLevel < 1 || compLevel > 9) @@ -1016,8 +1169,11 @@ protected OutputStream packet(OutputStream out, FileMetadata meta) */ protected SigningOutputStream sign(OutputStream out, FileMetadata meta) throws IOException, PGPException { + String fileName = (meta == null) ? null : meta.getName(); HashingAlgorithm sigAlg = getSigningAlgorithm(); - log.trace("using signing algorithm {}", sigAlg); + if (isLoggingEnabled()) { + log.trace("{}: using signing algorithm {}", fileName, sigAlg); + } if (sigAlg == HashingAlgorithm.Unsigned) return null; @@ -1029,7 +1185,9 @@ protected SigningOutputStream sign(OutputStream out, FileMetadata meta) Key key = signers.get(i); Subkey subkey = key.getSigning(); if (!isUsableForSigning(subkey)) { - log.info("not using signing key {}", subkey); + if (isLoggingEnabled()) { + log.debug("{}: not using signing key {}", fileName, subkey); + } signers.remove(i); } } @@ -1083,8 +1241,11 @@ protected PGPEncryptedDataGenerator buildEncryptor() { * Builds a PublicKeyKeyEncryptionMethodGenerator * for the specified key. */ - protected PublicKeyKeyEncryptionMethodGenerator buildPublicKeyEncryptor(Key key) { - log.info("using encryption key {}", key.getEncryption()); + protected PublicKeyKeyEncryptionMethodGenerator buildPublicKeyEncryptor(Key key, FileMetadata meta) { + if (isLoggingEnabled()) { + log.info("{}: using encryption key {}", + (meta == null) ? null : meta.getName(), key.getEncryption()); + } PGPPublicKey publicKey = key.getEncryption().getPublicKey(); return new BcPublicKeyKeyEncryptionMethodGenerator(publicKey); @@ -1092,14 +1253,16 @@ protected PublicKeyKeyEncryptionMethodGenerator buildPublicKeyEncryptor(Key key) /** * Builds a PublicKeyKeyEncryptionMethodGenerator - * for the specified key. + * for the specified key to encrypt the file. */ - protected PBEKeyEncryptionMethodGenerator buildSymmetricKeyEncryptor() + protected PBEKeyEncryptionMethodGenerator buildSymmetricKeyEncryptor(FileMetadata meta) throws PGPException { HashingAlgorithm kdAlgorithm = getKeyDeriviationAlgorithm(); int workFactor = getKeyDeriviationWorkFactor(); - log.info("using symmetric encryption with {} hash, work factor {}", - kdAlgorithm, workFactor); + if (isLoggingEnabled()) { + log.info("{}: using symmetric encryption with {} hash, work factor {}", + (meta == null) ? null : meta.getName(), kdAlgorithm, workFactor); + } return new BcPBEKeyEncryptionMethodGenerator( getSymmetricPassphraseChars(), @@ -1117,9 +1280,11 @@ protected boolean isUsableForSigning(Subkey subkey) { */ protected PGPSignatureGenerator buildSigner(Key key, FileMetadata meta) throws PGPException { + String fileName = (meta == null) ? null : meta.getName(); Subkey subkey = key.getSigning(); - - log.info("using signing key {}", subkey); + if (isLoggingEnabled()) { + log.info("{}: using signing key {}", fileName, key); + } PGPContentSignerBuilder builder = buildSignerBuilder( subkey.getPublicKey().getAlgorithm(), @@ -1131,7 +1296,9 @@ protected PGPSignatureGenerator buildSigner(Key key, FileMetadata meta) String uid = key.getSigningUid(); if (!Util.isEmpty(uid)) { - log.debug("using signing uid {}", uid); + if (isLoggingEnabled()) { + log.debug("{}: using signing uid {}", fileName, uid); + } PGPSignatureSubpacketGenerator signer = new PGPSignatureSubpacketGenerator(); diff --git a/src/main/java/org/c02e/jpgpj/FileMetadata.java b/src/main/java/org/c02e/jpgpj/FileMetadata.java index 62152ec..991d9f3 100644 --- a/src/main/java/org/c02e/jpgpj/FileMetadata.java +++ b/src/main/java/org/c02e/jpgpj/FileMetadata.java @@ -8,6 +8,7 @@ import java.nio.file.attribute.FileTime; import java.util.Date; import java.util.Objects; +import java.util.concurrent.TimeUnit; import org.bouncycastle.openpgp.PGPLiteralData; import org.bouncycastle.openpgp.PGPSignature; @@ -27,7 +28,7 @@ public enum Format { /** UTF-8 encoded text with CRLF line-endings. */ UTF8('u'); - protected char code; + private final char code; Format(char code) { this.code = code; @@ -50,11 +51,11 @@ public static Format byCode(char code) { public static final String DEFAULT_NAME = ""; public static final Format DEFAULT_FORMAT = Format.BINARY; - protected String name; - protected Format format; - protected long length; - protected long lastModified; - protected Ring verified = new Ring(); + private String name; + private Format format; + private long length; + private long lastModified; + private final Ring verified = new Ring(); /** Constructs a metadata object with default values. */ public FileMetadata() { @@ -244,6 +245,34 @@ public int getSignatureType() { PGPSignature.CANONICAL_TEXT_DOCUMENT : PGPSignature.BINARY_DOCUMENT; } + @Override + public int hashCode() { + return Objects.hash(getName(), getFormat()) + + 31 * Long.hashCode(getLength()) + + 37 * Long.hashCode(TimeUnit.MILLISECONDS.toSeconds(getLastModified())) + ; + } + + @Override + public boolean equals(Object o) { + if (o == null) { + return false; + } + if (o == this) { + return true; + } + if (getClass() != o.getClass()) { + return false; + } + + FileMetadata that = (FileMetadata) o; + return Objects.equals(getName(), that.getName()) + && Objects.equals(getFormat(), that.getFormat()) + && (getLength() == that.getLength()) + && (TimeUnit.MILLISECONDS.toSeconds(getLastModified()) == TimeUnit.MILLISECONDS.toSeconds(that.getLastModified())) + ; + } + @Override public String toString() { return getClass().getSimpleName() diff --git a/src/test/groovy/org/c02e/jpgpj/EncryptorSpec.groovy b/src/test/groovy/org/c02e/jpgpj/EncryptorSpec.groovy index 79b9c8d..9f370b6 100644 --- a/src/test/groovy/org/c02e/jpgpj/EncryptorSpec.groovy +++ b/src/test/groovy/org/c02e/jpgpj/EncryptorSpec.groovy @@ -236,6 +236,31 @@ class EncryptorSpec extends Specification { meta.verified.keys.signingUid == ['Test Key 1 '] } + def "encrypt bytes"() { + when: + def expected = "This is a test of bytes encoding" + def encryptor = new Encryptor(new Ring(stream('test-key-1.asc'))) + encryptor.ring.keys*.passphrase = 'c02e' + def encMeta = encryptor.encryptBytes expected.getBytes(), "bytesTest", cipherOut + + def decryptor = new Decryptor(new Ring(stream('test-key-1.asc'))) + decryptor.ring.keys*.passphrase = 'c02e' + def decMeta = decryptor.decrypt(cipherIn, plainOut) + + then: + plainOut.toString() == expected + + decMeta.name == "bytesTest" + decMeta.length == expected.length() + decMeta.format == FileMetadata.Format.BINARY + + decMeta == encMeta + + decMeta.verified + decMeta.verified.keys.uids == [['Test Key 1 ']] + decMeta.verified.keys.signingUid == ['Test Key 1 '] + } + def "encrypt and sign with ascii armor"() { when: def encryptor = new Encryptor(new Ring(stream('test-key-1.asc'))) @@ -381,6 +406,7 @@ hQEMAyne546XDHBhAQ... meta.verified } + def "encrypt and sign zero-byte file"() { when: def encryptor = new Encryptor(new Key(file('test-key-1.asc'), 'c02e')) @@ -913,8 +939,12 @@ hQEMAyne546XDHBhAQ... 'test\n' } + protected getPlainBytes() { + plainText.bytes + } + protected getPlainIn() { - new ByteArrayInputStream(plainText.bytes) + new ByteArrayInputStream(plainBytes) } protected getCipherIn() {