diff --git a/java/google/registry/config/RegistryConfig.java b/java/google/registry/config/RegistryConfig.java index 4abe9afc0..e13ad841c 100644 --- a/java/google/registry/config/RegistryConfig.java +++ b/java/google/registry/config/RegistryConfig.java @@ -672,20 +672,6 @@ public final class RegistryConfig { return config.rde.reportUrlPrefix; } - /** - * Size of RYDE generator buffer in bytes for each of the five layers. - * - * @see google.registry.rde.RydePgpCompressionOutputStream - * @see google.registry.rde.RydePgpFileOutputStream - * @see google.registry.rde.RydePgpSigningOutputStream - * @see google.registry.rde.RydeTarOutputStream - */ - @Provides - @Config("rdeRydeBufferSize") - public static Integer provideRdeRydeBufferSize() { - return 64 * 1024; - } - /** * Maximum amount of time generating an escrow deposit for a TLD could take, before killing. * diff --git a/java/google/registry/rde/BrdaCopyAction.java b/java/google/registry/rde/BrdaCopyAction.java index 7c5ffbe34..9c8e11325 100644 --- a/java/google/registry/rde/BrdaCopyAction.java +++ b/java/google/registry/rde/BrdaCopyAction.java @@ -64,11 +64,6 @@ public final class BrdaCopyAction implements Runnable { private static final FluentLogger logger = FluentLogger.forEnclosingClass(); @Inject GcsUtils gcsUtils; - @Inject RydePgpCompressionOutputStreamFactory pgpCompressionFactory; - @Inject RydePgpFileOutputStreamFactory pgpFileFactory; - @Inject RydePgpEncryptionOutputStreamFactory pgpEncryptionFactory; - @Inject RydePgpSigningOutputStreamFactory pgpSigningFactory; - @Inject RydeTarOutputStreamFactory tarFactory; @Inject @Config("brdaBucket") String brdaBucket; @Inject @Config("rdeBucket") String stagingBucket; @Inject @Parameter(RequestParameters.PARAM_TLD) String tld; @@ -96,25 +91,17 @@ public final class BrdaCopyAction implements Runnable { long xmlLength = readXmlLength(xmlLengthFilename); - logger.atInfo().log("Writing %s", rydeFile); - byte[] signature; + logger.atInfo().log("Writing %s and %s", rydeFile, sigFile); try (InputStream gcsInput = gcsUtils.openInputStream(xmlFilename); InputStream ghostrydeDecoder = Ghostryde.decoder(gcsInput, stagingDecryptionKey); - OutputStream gcsOutput = gcsUtils.openOutputStream(rydeFile); - RydePgpSigningOutputStream signLayer = pgpSigningFactory.create(gcsOutput, signingKey)) { - try (OutputStream encryptLayer = pgpEncryptionFactory.create(signLayer, receiverKey); - OutputStream compressLayer = pgpCompressionFactory.create(encryptLayer); - OutputStream fileLayer = pgpFileFactory.create(compressLayer, watermark, prefix + ".tar"); - OutputStream tarLayer = - tarFactory.create(fileLayer, xmlLength, watermark, prefix + ".xml")) { - ByteStreams.copy(ghostrydeDecoder, tarLayer); - } - signature = signLayer.getSignature(); - } - - logger.atInfo().log("Writing %s", sigFile); - try (OutputStream gcsOutput = gcsUtils.openOutputStream(sigFile)) { - gcsOutput.write(signature); + OutputStream rydeOut = gcsUtils.openOutputStream(rydeFile); + OutputStream sigOut = gcsUtils.openOutputStream(sigFile); + RydeEncoder rydeEncoder = new RydeEncoder.Builder() + .setRydeOutput(rydeOut, receiverKey) + .setSignatureOutput(sigOut, signingKey) + .setFileMetadata(prefix, xmlLength, watermark) + .build()) { + ByteStreams.copy(ghostrydeDecoder, rydeEncoder); } } diff --git a/java/google/registry/rde/RdeUploadAction.java b/java/google/registry/rde/RdeUploadAction.java index 54d6dbbde..385b3f223 100644 --- a/java/google/registry/rde/RdeUploadAction.java +++ b/java/google/registry/rde/RdeUploadAction.java @@ -56,6 +56,7 @@ import google.registry.util.Retrier; import google.registry.util.TaskQueueUtils; import google.registry.util.TeeOutputStream; import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -103,11 +104,6 @@ public final class RdeUploadAction implements Runnable, EscrowTask { @Inject JSchSshSessionFactory jschSshSessionFactory; @Inject Response response; - @Inject RydePgpCompressionOutputStreamFactory pgpCompressionFactory; - @Inject RydePgpEncryptionOutputStreamFactory pgpEncryptionFactory; - @Inject RydePgpFileOutputStreamFactory pgpFileFactory; - @Inject RydePgpSigningOutputStreamFactory pgpSigningFactory; - @Inject RydeTarOutputStreamFactory tarFactory; @Inject SftpProgressMonitor sftpProgressMonitor; @Inject TaskQueueUtils taskQueueUtils; @Inject Retrier retrier; @@ -214,28 +210,27 @@ public final class RdeUploadAction implements Runnable, EscrowTask { InputStream ghostrydeDecoder = Ghostryde.decoder(gcsInput, stagingDecryptionKey)) { try (JSchSshSession session = jschSshSessionFactory.create(lazyJsch.get(), uploadUrl); JSchSftpChannel ftpChan = session.openSftpChannel()) { - byte[] signature; + ByteArrayOutputStream sigOut = new ByteArrayOutputStream(); String rydeFilename = name + ".ryde"; GcsFilename rydeGcsFilename = new GcsFilename(bucket, rydeFilename); try (OutputStream ftpOutput = ftpChan.get().put(rydeFilename, sftpProgressMonitor, OVERWRITE); OutputStream gcsOutput = gcsUtils.openOutputStream(rydeGcsFilename); TeeOutputStream teeOutput = new TeeOutputStream(asList(ftpOutput, gcsOutput)); - RydePgpSigningOutputStream signer = pgpSigningFactory.create(teeOutput, signingKey)) { - try (OutputStream encryptLayer = pgpEncryptionFactory.create(signer, receiverKey); - OutputStream kompressor = pgpCompressionFactory.create(encryptLayer); - OutputStream fileLayer = pgpFileFactory.create(kompressor, watermark, name + ".tar"); - OutputStream tarLayer = - tarFactory.create(fileLayer, xmlLength, watermark, name + ".xml")) { - ByteStreams.copy(ghostrydeDecoder, tarLayer); + RydeEncoder rydeEncoder = + new RydeEncoder.Builder() + .setRydeOutput(teeOutput, receiverKey) + .setSignatureOutput(sigOut, signingKey) + .setFileMetadata(name, xmlLength, watermark) + .build()) { + long bytesCopied = ByteStreams.copy(ghostrydeDecoder, rydeEncoder); + logger.atInfo().log("uploaded %,d bytes: %s", bytesCopied, rydeFilename); } - signature = signer.getSignature(); - logger.atInfo().log("uploaded %,d bytes: %s.ryde", signer.getBytesWritten(), name); - } String sigFilename = name + ".sig"; + byte[] signature = sigOut.toByteArray(); gcsUtils.createFromBytes(new GcsFilename(bucket, sigFilename), signature); ftpChan.get().put(new ByteArrayInputStream(signature), sigFilename); - logger.atInfo().log("uploaded %,d bytes: %s.sig", signature.length, name); + logger.atInfo().log("uploaded %,d bytes: %s", signature.length, sigFilename); } } } diff --git a/java/google/registry/rde/RydeEncoder.java b/java/google/registry/rde/RydeEncoder.java new file mode 100644 index 000000000..11e48154e --- /dev/null +++ b/java/google/registry/rde/RydeEncoder.java @@ -0,0 +1,153 @@ +// Copyright 2018 The Nomulus Authors. All Rights Reserved. +// +// 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 google.registry.rde; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.collect.ImmutableList; +import com.google.common.io.Closer; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Collection; +import javax.annotation.concurrent.NotThreadSafe; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPKeyPair; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.joda.time.DateTime; + +/** + * Stream that performs the full RyDE encryption. + * + *

The RyDE format has 2 files: + * + *

+ * + *

Hence, the encoder needs to receive 2 OutputStreams - one for the data and one for the + * signature. + * + *

Because of the external tar file encoding - the encoder must know the total length of the data + * from the start. This is a bit annoying, but necessary. + */ +@NotThreadSafe +public final class RydeEncoder extends FilterOutputStream { + + private final OutputStream sigOutput; + private final RydePgpSigningOutputStream signer; + private final OutputStream encryptLayer; + private final OutputStream kompressor; + private final OutputStream fileLayer; + private final OutputStream tarLayer; + // We use a Closer to handle the stream .close, to make sure it's done correctly. + private final Closer closer = Closer.create(); + private boolean isClosed = false; + + private RydeEncoder( + OutputStream rydeOutput, + OutputStream sigOutput, + long dataLength, + String filenamePrefix, + DateTime modified, + PGPKeyPair signingKey, + Collection receiverKeys) { + super(null); + this.sigOutput = sigOutput; + signer = closer.register(new RydePgpSigningOutputStream(checkNotNull(rydeOutput), signingKey)); + encryptLayer = closer.register(new RydePgpEncryptionOutputStream(signer, receiverKeys)); + kompressor = closer.register(new RydePgpCompressionOutputStream(encryptLayer)); + fileLayer = + closer.register(new RydePgpFileOutputStream(kompressor, modified, filenamePrefix + ".tar")); + tarLayer = + closer.register( + new RydeTarOutputStream(fileLayer, dataLength, modified, filenamePrefix + ".xml")); + this.out = tarLayer; + } + + /** + * Call the underlying 3 input write. + * + *

FilterInputStream implements the 3 input write using a for loop over the single-byte write. + * For efficiency reasons, we want it to use the 3 input write instead. + */ + @Override + public void write(byte[] b, int off, int len) throws IOException { + out.write(b, off, len); + } + + @Override + public void close() throws IOException { + if (isClosed) { + return; + } + // Close all the streams we opened + closer.close(); + isClosed = true; + try { + sigOutput.write(signer.getSignature()); + } catch (PGPException e) { + throw new RuntimeException("Failed to generate signature", e); + } + } + + /** Builder for {@link RydeEncoder}. */ + public static class Builder { + OutputStream rydeOutput; + OutputStream sigOutput; + Long dataLength; + String filenamePrefix; + DateTime modified; + PGPKeyPair signingKey; + ImmutableList receiverKeys; + + /** Sets the OutputStream for the Ryde-encoded data, and the keys used for the encryption. */ + public Builder setRydeOutput( + OutputStream rydeOutput, PGPPublicKey receiverKey, PGPPublicKey... moreReceiverKeys) { + this.rydeOutput = rydeOutput; + this.receiverKeys = + new ImmutableList.Builder().add(receiverKey).add(moreReceiverKeys).build(); + return this; + } + + /** Sets the OutputStream for the signature, and the key used to sign. */ + public Builder setSignatureOutput(OutputStream sigOutput, PGPKeyPair signingKey) { + this.sigOutput = sigOutput; + this.signingKey = signingKey; + return this; + } + + /** Sets the information about the unencoded data that will follow. */ + public Builder setFileMetadata(String filenamePrefix, long dataLength, DateTime modified) { + this.filenamePrefix = filenamePrefix; + this.dataLength = dataLength; + this.modified = modified; + return this; + } + + /** Returns the built {@link RydeEncoder}. */ + public RydeEncoder build() { + return new RydeEncoder( + checkNotNull(rydeOutput, "Must call 'setRydeOutput'"), + checkNotNull(sigOutput, "Must call 'setSignatureOutput'"), + checkNotNull(dataLength, "Must call 'setFileMetadata'"), + checkNotNull(filenamePrefix, "Must call 'setFileMetadata'"), + checkNotNull(modified, "Must call 'setFileMetadata'"), + checkNotNull(signingKey, "Must call 'setSignatureOutput'"), + checkNotNull(receiverKeys, "Must call 'setRydeOutput'")); + } + } +} diff --git a/java/google/registry/rde/RydePgpCompressionOutputStream.java b/java/google/registry/rde/RydePgpCompressionOutputStream.java index 9876bfa99..e0422b07d 100644 --- a/java/google/registry/rde/RydePgpCompressionOutputStream.java +++ b/java/google/registry/rde/RydePgpCompressionOutputStream.java @@ -16,9 +16,6 @@ package google.registry.rde; import static org.bouncycastle.bcpg.CompressionAlgorithmTags.ZIP; -import com.google.auto.factory.AutoFactory; -import com.google.auto.factory.Provided; -import google.registry.config.RegistryConfig.Config; import google.registry.util.ImprovedOutputStream; import java.io.IOException; import java.io.OutputStream; @@ -31,9 +28,10 @@ import org.bouncycastle.openpgp.PGPException; * *

This uses the ZIP compression algorithm per the ICANN escrow specification. */ -@AutoFactory(allowSubclasses = true) public class RydePgpCompressionOutputStream extends ImprovedOutputStream { + private static final int BUFFER_SIZE = 64 * 1024; + /** * Creates a new instance that compresses data. * @@ -41,9 +39,8 @@ public class RydePgpCompressionOutputStream extends ImprovedOutputStream { * @throws RuntimeException to rethrow {@link PGPException} and {@link IOException} */ public RydePgpCompressionOutputStream( - @Provided @Config("rdeRydeBufferSize") Integer bufferSize, @WillNotClose OutputStream os) { - super("RydePgpCompressionOutputStream", createDelegate(bufferSize, os)); + super("RydePgpCompressionOutputStream", createDelegate(BUFFER_SIZE, os)); } private static OutputStream createDelegate(int bufferSize, OutputStream os) { diff --git a/java/google/registry/rde/RydePgpEncryptionOutputStream.java b/java/google/registry/rde/RydePgpEncryptionOutputStream.java index 85b1d3078..ca108dd3c 100644 --- a/java/google/registry/rde/RydePgpEncryptionOutputStream.java +++ b/java/google/registry/rde/RydePgpEncryptionOutputStream.java @@ -14,18 +14,17 @@ package google.registry.rde; +import static com.google.common.base.Preconditions.checkArgument; import static org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags.AES_128; import static org.bouncycastle.jce.provider.BouncyCastleProvider.PROVIDER_NAME; -import com.google.auto.factory.AutoFactory; -import com.google.auto.factory.Provided; -import google.registry.config.RegistryConfig.Config; import google.registry.util.ImprovedOutputStream; import java.io.IOException; import java.io.OutputStream; import java.security.NoSuchAlgorithmException; import java.security.ProviderException; import java.security.SecureRandom; +import java.util.Collection; import javax.annotation.WillNotClose; import org.bouncycastle.openpgp.PGPEncryptedDataGenerator; import org.bouncycastle.openpgp.PGPException; @@ -53,9 +52,10 @@ import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodG * @see RFC 4880 (OpenPGP Message Format) * @see AES (Wikipedia) */ -@AutoFactory(allowSubclasses = true) public class RydePgpEncryptionOutputStream extends ImprovedOutputStream { + private static final int BUFFER_SIZE = 64 * 1024; + /** * The symmetric encryption algorithm to use. Do not change this value without checking the * RFCs to make sure the encryption algorithm and strength combination is allowed. @@ -92,22 +92,23 @@ public class RydePgpEncryptionOutputStream extends ImprovedOutputStream { * @throws RuntimeException to rethrow {@link PGPException} and {@link IOException} */ public RydePgpEncryptionOutputStream( - @Provided @Config("rdeRydeBufferSize") Integer bufferSize, @WillNotClose OutputStream os, - PGPPublicKey receiverKey) { - super("RydePgpEncryptionOutputStream", createDelegate(bufferSize, os, receiverKey)); + Collection receiverKeys) { + super("RydePgpEncryptionOutputStream", createDelegate(os, receiverKeys)); } - private static - OutputStream createDelegate(int bufferSize, OutputStream os, PGPPublicKey receiverKey) { + private static OutputStream createDelegate( + OutputStream os, Collection receiverKeys) { try { PGPEncryptedDataGenerator encryptor = new PGPEncryptedDataGenerator( new JcePGPDataEncryptorBuilder(CIPHER) .setWithIntegrityPacket(USE_INTEGRITY_PACKET) .setSecureRandom(SecureRandom.getInstance(RANDOM_SOURCE)) .setProvider(PROVIDER_NAME)); - encryptor.addMethod(new JcePublicKeyKeyEncryptionMethodGenerator(receiverKey)); - return encryptor.open(os, new byte[bufferSize]); + checkArgument(!receiverKeys.isEmpty(), "Must give at least one receiver key"); + receiverKeys.forEach( + key -> encryptor.addMethod(new JcePublicKeyKeyEncryptionMethodGenerator(key))); + return encryptor.open(os, new byte[BUFFER_SIZE]); } catch (NoSuchAlgorithmException e) { throw new ProviderException(e); } catch (IOException | PGPException e) { diff --git a/java/google/registry/rde/RydePgpFileOutputStream.java b/java/google/registry/rde/RydePgpFileOutputStream.java index 6f1887ef4..31d63fe6b 100644 --- a/java/google/registry/rde/RydePgpFileOutputStream.java +++ b/java/google/registry/rde/RydePgpFileOutputStream.java @@ -17,9 +17,6 @@ package google.registry.rde; import static com.google.common.base.Preconditions.checkArgument; import static org.bouncycastle.openpgp.PGPLiteralData.BINARY; -import com.google.auto.factory.AutoFactory; -import com.google.auto.factory.Provided; -import google.registry.config.RegistryConfig.Config; import google.registry.util.ImprovedOutputStream; import java.io.IOException; import java.io.OutputStream; @@ -36,9 +33,10 @@ import org.joda.time.DateTime; * *

According to escrow spec, the PGP message should contain a single tar file. */ -@AutoFactory(allowSubclasses = true) public class RydePgpFileOutputStream extends ImprovedOutputStream { + private static final int BUFFER_SIZE = 64 * 1024; + /** * Creates a new instance for a particular file. * @@ -47,20 +45,19 @@ public class RydePgpFileOutputStream extends ImprovedOutputStream { * @throws RuntimeException to rethrow {@link IOException} */ public RydePgpFileOutputStream( - @Provided @Config("rdeRydeBufferSize") Integer bufferSize, @WillNotClose OutputStream os, DateTime modified, String filename) { - super("RydePgpFileOutputStream", createDelegate(bufferSize, os, modified, filename)); + super("RydePgpFileOutputStream", createDelegate(os, modified, filename)); } private static OutputStream - createDelegate(int bufferSize, OutputStream os, DateTime modified, String filename) { + createDelegate(OutputStream os, DateTime modified, String filename) { try { checkArgument(filename.endsWith(".tar"), "Ryde PGP message should contain a tar file."); return new PGPLiteralDataGenerator().open( - os, BINARY, filename, modified.toDate(), new byte[bufferSize]); + os, BINARY, filename, modified.toDate(), new byte[BUFFER_SIZE]); } catch (IOException e) { throw new RuntimeException(e); } diff --git a/java/google/registry/rde/RydePgpSigningOutputStream.java b/java/google/registry/rde/RydePgpSigningOutputStream.java index 0e8b1aaf4..7a9ccb127 100644 --- a/java/google/registry/rde/RydePgpSigningOutputStream.java +++ b/java/google/registry/rde/RydePgpSigningOutputStream.java @@ -18,7 +18,6 @@ import static org.bouncycastle.bcpg.HashAlgorithmTags.SHA256; import static org.bouncycastle.bcpg.PublicKeyAlgorithmTags.RSA_GENERAL; import static org.bouncycastle.openpgp.PGPSignature.BINARY_DOCUMENT; -import com.google.auto.factory.AutoFactory; import google.registry.util.ImprovedOutputStream; import java.io.IOException; import java.io.OutputStream; @@ -40,7 +39,6 @@ import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder; * who receive a deposit to check the signature against our public key so they can know the * data hasn't been forged. */ -@AutoFactory(allowSubclasses = true) public class RydePgpSigningOutputStream extends ImprovedOutputStream { private final PGPSignatureGenerator signer; diff --git a/java/google/registry/rde/RydeTarOutputStream.java b/java/google/registry/rde/RydeTarOutputStream.java index eb2671946..5ba47348d 100644 --- a/java/google/registry/rde/RydeTarOutputStream.java +++ b/java/google/registry/rde/RydeTarOutputStream.java @@ -16,7 +16,6 @@ package google.registry.rde; import static com.google.common.base.Preconditions.checkArgument; -import com.google.auto.factory.AutoFactory; import google.registry.util.ImprovedOutputStream; import google.registry.util.PosixTarHeader; import java.io.IOException; @@ -27,7 +26,6 @@ import org.joda.time.DateTime; /** * Single-file POSIX tar archive creator that wraps an {@link OutputStream}. */ -@AutoFactory(allowSubclasses = true) public class RydeTarOutputStream extends ImprovedOutputStream { private final long expectedSize; diff --git a/java/google/registry/tools/EscrowDepositEncryptor.java b/java/google/registry/tools/EscrowDepositEncryptor.java index c1f025ed7..da840d5e0 100644 --- a/java/google/registry/tools/EscrowDepositEncryptor.java +++ b/java/google/registry/tools/EscrowDepositEncryptor.java @@ -20,16 +20,7 @@ import com.google.common.io.ByteStreams; import google.registry.keyring.api.KeyModule.Key; import google.registry.model.rde.RdeNamingUtils; import google.registry.rde.RdeUtil; -import google.registry.rde.RydePgpCompressionOutputStream; -import google.registry.rde.RydePgpCompressionOutputStreamFactory; -import google.registry.rde.RydePgpEncryptionOutputStream; -import google.registry.rde.RydePgpEncryptionOutputStreamFactory; -import google.registry.rde.RydePgpFileOutputStream; -import google.registry.rde.RydePgpFileOutputStreamFactory; -import google.registry.rde.RydePgpSigningOutputStream; -import google.registry.rde.RydePgpSigningOutputStreamFactory; -import google.registry.rde.RydeTarOutputStream; -import google.registry.rde.RydeTarOutputStreamFactory; +import google.registry.rde.RydeEncoder; import google.registry.xml.XmlException; import java.io.BufferedInputStream; import java.io.IOException; @@ -40,7 +31,6 @@ import java.nio.file.Path; import javax.inject.Inject; import javax.inject.Provider; import org.bouncycastle.bcpg.ArmoredOutputStream; -import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPKeyPair; import org.bouncycastle.openpgp.PGPPublicKey; import org.joda.time.DateTime; @@ -50,18 +40,13 @@ final class EscrowDepositEncryptor { private static final int PEEK_BUFFER_SIZE = 64 * 1024; - @Inject RydePgpCompressionOutputStreamFactory pgpCompressionFactory; - @Inject RydePgpEncryptionOutputStreamFactory pgpEncryptionFactory; - @Inject RydePgpFileOutputStreamFactory pgpFileFactory; - @Inject RydePgpSigningOutputStreamFactory pgpSigningFactory; - @Inject RydeTarOutputStreamFactory tarFactory; @Inject @Key("rdeSigningKey") Provider rdeSigningKey; @Inject @Key("rdeReceiverKey") Provider rdeReceiverKey; @Inject EscrowDepositEncryptor() {} /** Creates a {@code .ryde} and {@code .sig} file, provided an XML deposit file. */ void encrypt(String tld, Path xmlFile, Path outdir) - throws IOException, PGPException, XmlException { + throws IOException, XmlException { try (InputStream xmlFileInput = Files.newInputStream(xmlFile); BufferedInputStream xmlInput = new BufferedInputStream(xmlFileInput, PEEK_BUFFER_SIZE)) { DateTime watermark = RdeUtil.peekWatermark(xmlInput); @@ -71,23 +56,17 @@ final class EscrowDepositEncryptor { Path pubPath = outdir.resolve(tld + ".pub"); PGPKeyPair signingKey = rdeSigningKey.get(); try (OutputStream rydeOutput = Files.newOutputStream(rydePath); - RydePgpSigningOutputStream signLayer = - pgpSigningFactory.create(rydeOutput, signingKey)) { - try (RydePgpEncryptionOutputStream encryptLayer = - pgpEncryptionFactory.create(signLayer, rdeReceiverKey.get()); - RydePgpCompressionOutputStream compressLayer = - pgpCompressionFactory.create(encryptLayer); - RydePgpFileOutputStream fileLayer = - pgpFileFactory.create(compressLayer, watermark, name + ".tar"); - RydeTarOutputStream tarLayer = - tarFactory.create(fileLayer, Files.size(xmlFile), watermark, name + ".xml")) { - ByteStreams.copy(xmlInput, tarLayer); - } - Files.write(sigPath, signLayer.getSignature()); - try (OutputStream pubOutput = Files.newOutputStream(pubPath); - ArmoredOutputStream ascOutput = new ArmoredOutputStream(pubOutput)) { - signingKey.getPublicKey().encode(ascOutput); - } + OutputStream sigOutput = Files.newOutputStream(sigPath); + RydeEncoder rydeEncoder = new RydeEncoder.Builder() + .setRydeOutput(rydeOutput, rdeReceiverKey.get()) + .setSignatureOutput(sigOutput, signingKey) + .setFileMetadata(name, Files.size(xmlFile), watermark) + .build()) { + ByteStreams.copy(xmlInput, rydeEncoder); + } + try (OutputStream pubOutput = Files.newOutputStream(pubPath); + ArmoredOutputStream ascOutput = new ArmoredOutputStream(pubOutput)) { + signingKey.getPublicKey().encode(ascOutput); } } } diff --git a/javatests/google/registry/rde/BrdaCopyActionTest.java b/javatests/google/registry/rde/BrdaCopyActionTest.java index 272960e1f..4406526f7 100644 --- a/javatests/google/registry/rde/BrdaCopyActionTest.java +++ b/javatests/google/registry/rde/BrdaCopyActionTest.java @@ -100,11 +100,6 @@ public class BrdaCopyActionTest extends ShardableTestCase { @Before public void before() throws Exception { action.gcsUtils = gcsUtils; - action.pgpCompressionFactory = new RydePgpCompressionOutputStreamFactory(() -> 1024); - action.pgpEncryptionFactory = new RydePgpEncryptionOutputStreamFactory(() -> 1024); - action.pgpFileFactory = new RydePgpFileOutputStreamFactory(() -> 1024); - action.pgpSigningFactory = new RydePgpSigningOutputStreamFactory(); - action.tarFactory = new RydeTarOutputStreamFactory(); action.tld = "lol"; action.watermark = DateTime.parse("2010-10-17TZ"); action.brdaBucket = "tub"; diff --git a/javatests/google/registry/rde/RdeUploadActionTest.java b/javatests/google/registry/rde/RdeUploadActionTest.java index 1bcc90b9d..b110c26a6 100644 --- a/javatests/google/registry/rde/RdeUploadActionTest.java +++ b/javatests/google/registry/rde/RdeUploadActionTest.java @@ -70,7 +70,6 @@ import google.registry.testing.FakeKeyringModule; import google.registry.testing.FakeResponse; import google.registry.testing.FakeSleeper; import google.registry.testing.GpgSystemCommandRule; -import google.registry.testing.IoSpyRule; import google.registry.testing.Lazies; import google.registry.testing.TaskQueueHelper.TaskMatcher; import google.registry.testing.sftp.SftpServerRule; @@ -81,10 +80,8 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.io.OutputStream; import java.net.Socket; import java.net.URI; -import org.bouncycastle.openpgp.PGPKeyPair; import org.bouncycastle.openpgp.PGPPublicKey; import org.joda.time.DateTime; import org.junit.Before; @@ -131,11 +128,6 @@ public class RdeUploadActionTest { RdeTestData.loadBytes("pgp-public-keyring.asc"), RdeTestData.loadBytes("pgp-private-keyring-escrow.asc")); - @Rule - public final IoSpyRule ioSpy = new IoSpyRule() - .checkClosedOnlyOnce() - .checkCharIoMaxCalls(10); - @Rule public final AppEngineRule appEngine = AppEngineRule.builder() .withDatastore() @@ -146,42 +138,6 @@ public class RdeUploadActionTest { private final EscrowTaskRunner runner = mock(EscrowTaskRunner.class); private final FakeClock clock = new FakeClock(DateTime.parse("2010-10-17TZ")); - private final RydeTarOutputStreamFactory tarFactory = - new RydeTarOutputStreamFactory() { - @Override - public RydeTarOutputStream create( - OutputStream os, long size, DateTime modified, String filename) { - return ioSpy.register(super.create(os, size, modified, filename)); - }}; - - private final RydePgpFileOutputStreamFactory literalFactory = - new RydePgpFileOutputStreamFactory(() -> BUFFER_SIZE) { - @Override - public RydePgpFileOutputStream create(OutputStream os, DateTime modified, String filename) { - return ioSpy.register(super.create(os, modified, filename)); - }}; - - private final RydePgpEncryptionOutputStreamFactory encryptFactory = - new RydePgpEncryptionOutputStreamFactory(() -> BUFFER_SIZE) { - @Override - public RydePgpEncryptionOutputStream create(OutputStream os, PGPPublicKey publicKey) { - return ioSpy.register(super.create(os, publicKey)); - }}; - - private final RydePgpCompressionOutputStreamFactory compressFactory = - new RydePgpCompressionOutputStreamFactory(() -> BUFFER_SIZE) { - @Override - public RydePgpCompressionOutputStream create(OutputStream os) { - return ioSpy.register(super.create(os)); - }}; - - private final RydePgpSigningOutputStreamFactory signFactory = - new RydePgpSigningOutputStreamFactory() { - @Override - public RydePgpSigningOutputStream create(OutputStream os, PGPKeyPair signingKey) { - return ioSpy.register(super.create(os, signingKey)); - }}; - private RdeUploadAction createAction(URI uploadUrl) { try (Keyring keyring = new FakeKeyringModule().get()) { RdeUploadAction action = new RdeUploadAction(); @@ -195,11 +151,6 @@ public class RdeUploadActionTest { keyring.getRdeSshClientPublicKey()); action.jschSshSessionFactory = new JSchSshSessionFactory(standardSeconds(3)); action.response = response; - action.pgpCompressionFactory = compressFactory; - action.pgpEncryptionFactory = encryptFactory; - action.pgpFileFactory = literalFactory; - action.pgpSigningFactory = signFactory; - action.tarFactory = tarFactory; action.bucket = "bucket"; action.interval = standardDays(1); action.timeout = standardSeconds(23); diff --git a/javatests/google/registry/rde/RydeGpgIntegrationTest.java b/javatests/google/registry/rde/RydeGpgIntegrationTest.java index 2f6dada15..d6cfd7b6c 100644 --- a/javatests/google/registry/rde/RydeGpgIntegrationTest.java +++ b/javatests/google/registry/rde/RydeGpgIntegrationTest.java @@ -68,12 +68,6 @@ public class RydeGpgIntegrationTest extends ShardableTestCase { new GpgCommand("gpg2"), }; - @DataPoints - public static BufferSize[] bufferSizes = new BufferSize[] { - new BufferSize(1), - new BufferSize(7), - }; - @DataPoints public static Filename[] filenames = new Filename[] { new Filename("sloth"), @@ -88,22 +82,11 @@ public class RydeGpgIntegrationTest extends ShardableTestCase { }; @Theory - public void test(GpgCommand cmd, BufferSize bufSize, Filename name, Content content) + public void test(GpgCommand cmd, Filename name, Content content) throws Exception { assumeTrue(hasCommand("tar")); assumeTrue(hasCommand(cmd.get() + " --version")); - RydeTarOutputStreamFactory tarFactory = - new RydeTarOutputStreamFactory(); - RydePgpFileOutputStreamFactory pgpFileFactory = - new RydePgpFileOutputStreamFactory(bufSize::get); - RydePgpEncryptionOutputStreamFactory pgpEncryptionFactory = - new RydePgpEncryptionOutputStreamFactory(bufSize::get); - RydePgpCompressionOutputStreamFactory pgpCompressionFactory = - new RydePgpCompressionOutputStreamFactory(bufSize::get); - RydePgpSigningOutputStreamFactory pgpSigningFactory = - new RydePgpSigningOutputStreamFactory(); - Keyring keyring = keyringFactory.get(); PGPKeyPair signingKey = keyring.getRdeSigningKey(); PGPPublicKey receiverKey = keyring.getRdeReceiverKey(); @@ -116,20 +99,13 @@ public class RydeGpgIntegrationTest extends ShardableTestCase { byte[] data = content.get().getBytes(UTF_8); try (OutputStream rydeOut = new FileOutputStream(rydeFile); - RydePgpSigningOutputStream signLayer = pgpSigningFactory.create(rydeOut, signingKey)) { - try (RydePgpEncryptionOutputStream encryptLayer = - pgpEncryptionFactory.create(signLayer, receiverKey); - RydePgpCompressionOutputStream compressLayer = - pgpCompressionFactory.create(encryptLayer); - RydePgpFileOutputStream fileLayer = - pgpFileFactory.create(compressLayer, modified, name.get() + ".tar"); - RydeTarOutputStream tarLayer = - tarFactory.create(fileLayer, data.length, modified, name.get() + ".xml")) { - tarLayer.write(data); - } - try (OutputStream sigOut = new FileOutputStream(sigFile)) { - sigOut.write(signLayer.getSignature()); - } + OutputStream sigOut = new FileOutputStream(sigFile); + RydeEncoder rydeEncoder = new RydeEncoder.Builder() + .setRydeOutput(rydeOut, receiverKey) + .setSignatureOutput(sigOut, signingKey) + .setFileMetadata(name.get(), data.length, modified) + .build()) { + rydeEncoder.write(data); } // Iron Mountain examines the ryde file to see what sort of OpenPGP layers it contains. @@ -252,18 +228,6 @@ public class RydeGpgIntegrationTest extends ShardableTestCase { } } - private static class BufferSize { - private final int value; - - BufferSize(int value) { - this.value = value; - } - - int get() { - return value; - } - } - private static class Filename { private final String value; diff --git a/javatests/google/registry/testing/IoSpyRule.java b/javatests/google/registry/testing/IoSpyRule.java deleted file mode 100644 index dcc4f0e08..000000000 --- a/javatests/google/registry/testing/IoSpyRule.java +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright 2017 The Nomulus Authors. All Rights Reserved. -// -// 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 google.registry.testing; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkState; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.atMost; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -import java.io.Closeable; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.List; -import javax.annotation.CheckReturnValue; -import org.junit.rules.ExternalResource; - -/** JUnit Rule that uses Mockito to spy on I/O streams to make sure they're healthy. */ -public final class IoSpyRule extends ExternalResource { - - private boolean checkClosedOnlyOnce; - private boolean checkClosedAtLeastOnce; - private int checkCharIoMaxCalls = -1; - private final List spiedCloseables = new ArrayList<>(); - private final List spiedInputStreams = new ArrayList<>(); - private final List spiedOutputStreams = new ArrayList<>(); - - public IoSpyRule() {} - - /** - * Enables check where {@link Closeable#close() close} must be called EXACTLY once. - * - *

This is sort of pedantic, since Java's contract for close specifies that it must permit - * multiple calls. - */ - public IoSpyRule checkClosedOnlyOnce() { - checkState(!checkClosedAtLeastOnce, "you're already using checkClosedAtLeastOnce()"); - checkClosedOnlyOnce = true; - return this; - } - - /** Enables check where {@link Closeable#close() close} must be called at least once. */ - public IoSpyRule checkClosedAtLeastOnce() { - checkState(!checkClosedOnlyOnce, "you're already using checkClosedOnlyOnce()"); - checkClosedAtLeastOnce = true; - return this; - } - - /** Enables check to make sure your streams aren't going too slow with char-based I/O. */ - public IoSpyRule checkCharIoMaxCalls(int value) { - checkArgument(value >= 0, "value >= 0"); - checkCharIoMaxCalls = value; - return this; - } - - /** Adds your {@link Closeable} to the list of streams to check, and returns its mocked self. */ - @CheckReturnValue - public T register(T stream) { - T res = spy(stream); - spiedCloseables.add(res); - if (stream instanceof InputStream) { - spiedInputStreams.add((InputStream) res); - } - if (stream instanceof OutputStream) { - spiedOutputStreams.add((OutputStream) res); - } - return res; - } - - @Override - protected void after() { - checkState(checkClosedOnlyOnce - || checkClosedAtLeastOnce - || checkCharIoMaxCalls != -1, - "At least one check must be enabled."); - try { - check(); - } catch (IOException e) { - throw new RuntimeException(e); - } finally { - spiedCloseables.clear(); - spiedInputStreams.clear(); - spiedOutputStreams.clear(); - } - } - - private void check() throws IOException { - for (Closeable stream : spiedCloseables) { - if (checkClosedAtLeastOnce) { - verify(stream, atLeastOnce()).close(); - } else if (checkClosedOnlyOnce) { - verify(stream, times(1)).close(); - } - } - if (checkCharIoMaxCalls != -1) { - for (InputStream stream : spiedInputStreams) { - verify(stream, atMost(checkCharIoMaxCalls)).read(); - } - for (OutputStream stream : spiedOutputStreams) { - verify(stream, atMost(checkCharIoMaxCalls)).write(anyInt()); - } - } - } -} diff --git a/javatests/google/registry/tools/EncryptEscrowDepositCommandTest.java b/javatests/google/registry/tools/EncryptEscrowDepositCommandTest.java index c5b257169..b0f72517b 100644 --- a/javatests/google/registry/tools/EncryptEscrowDepositCommandTest.java +++ b/javatests/google/registry/tools/EncryptEscrowDepositCommandTest.java @@ -20,11 +20,6 @@ import static google.registry.testing.TestDataHelper.loadBytes; import com.google.common.io.ByteSource; import com.google.common.io.Files; import google.registry.rde.RdeTestData; -import google.registry.rde.RydePgpCompressionOutputStreamFactory; -import google.registry.rde.RydePgpEncryptionOutputStreamFactory; -import google.registry.rde.RydePgpFileOutputStreamFactory; -import google.registry.rde.RydePgpSigningOutputStreamFactory; -import google.registry.rde.RydeTarOutputStreamFactory; import google.registry.testing.BouncyCastleProviderRule; import google.registry.testing.FakeKeyringModule; import java.io.File; @@ -43,11 +38,6 @@ public class EncryptEscrowDepositCommandTest static EscrowDepositEncryptor createEncryptor() { EscrowDepositEncryptor res = new EscrowDepositEncryptor(); - res.pgpCompressionFactory = new RydePgpCompressionOutputStreamFactory(() -> 1024); - res.pgpEncryptionFactory = new RydePgpEncryptionOutputStreamFactory(() -> 1024); - res.pgpFileFactory = new RydePgpFileOutputStreamFactory(() -> 1024); - res.pgpSigningFactory = new RydePgpSigningOutputStreamFactory(); - res.tarFactory = new RydeTarOutputStreamFactory(); res.rdeReceiverKey = () -> new FakeKeyringModule().get().getRdeReceiverKey(); res.rdeSigningKey = () -> new FakeKeyringModule().get().getRdeSigningKey(); return res;