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 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 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 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 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