Simplify the RyDE API

Second step of RDE encoding refactoring.

Creates a single OutputStream encode RyDE files.
This replaces the 5 OutputStreams that were needed before.

Also removes all the factories that were injected. It's an encoding, there's no point in injecting it.

Finally, removed the buffer-size configuration and replaced with a static final
const value in each individual OutputStream.

This doesn't yet include a decoder (InputStream). And there's still a lot of overlap between the Ryde and the Ghostryde code. Both of those are left for the next CLs.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=204898369
This commit is contained in:
guyben 2018-07-17 05:50:04 -07:00 committed by jianglai
parent c4a2b5fa8d
commit 8ec2eaf39c
15 changed files with 215 additions and 345 deletions

View file

@ -672,20 +672,6 @@ public final class RegistryConfig {
return config.rde.reportUrlPrefix; 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. * Maximum amount of time generating an escrow deposit for a TLD could take, before killing.
* *

View file

@ -64,11 +64,6 @@ public final class BrdaCopyAction implements Runnable {
private static final FluentLogger logger = FluentLogger.forEnclosingClass(); private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@Inject GcsUtils gcsUtils; @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("brdaBucket") String brdaBucket;
@Inject @Config("rdeBucket") String stagingBucket; @Inject @Config("rdeBucket") String stagingBucket;
@Inject @Parameter(RequestParameters.PARAM_TLD) String tld; @Inject @Parameter(RequestParameters.PARAM_TLD) String tld;
@ -96,25 +91,17 @@ public final class BrdaCopyAction implements Runnable {
long xmlLength = readXmlLength(xmlLengthFilename); long xmlLength = readXmlLength(xmlLengthFilename);
logger.atInfo().log("Writing %s", rydeFile); logger.atInfo().log("Writing %s and %s", rydeFile, sigFile);
byte[] signature;
try (InputStream gcsInput = gcsUtils.openInputStream(xmlFilename); try (InputStream gcsInput = gcsUtils.openInputStream(xmlFilename);
InputStream ghostrydeDecoder = Ghostryde.decoder(gcsInput, stagingDecryptionKey); InputStream ghostrydeDecoder = Ghostryde.decoder(gcsInput, stagingDecryptionKey);
OutputStream gcsOutput = gcsUtils.openOutputStream(rydeFile); OutputStream rydeOut = gcsUtils.openOutputStream(rydeFile);
RydePgpSigningOutputStream signLayer = pgpSigningFactory.create(gcsOutput, signingKey)) { OutputStream sigOut = gcsUtils.openOutputStream(sigFile);
try (OutputStream encryptLayer = pgpEncryptionFactory.create(signLayer, receiverKey); RydeEncoder rydeEncoder = new RydeEncoder.Builder()
OutputStream compressLayer = pgpCompressionFactory.create(encryptLayer); .setRydeOutput(rydeOut, receiverKey)
OutputStream fileLayer = pgpFileFactory.create(compressLayer, watermark, prefix + ".tar"); .setSignatureOutput(sigOut, signingKey)
OutputStream tarLayer = .setFileMetadata(prefix, xmlLength, watermark)
tarFactory.create(fileLayer, xmlLength, watermark, prefix + ".xml")) { .build()) {
ByteStreams.copy(ghostrydeDecoder, tarLayer); ByteStreams.copy(ghostrydeDecoder, rydeEncoder);
}
signature = signLayer.getSignature();
}
logger.atInfo().log("Writing %s", sigFile);
try (OutputStream gcsOutput = gcsUtils.openOutputStream(sigFile)) {
gcsOutput.write(signature);
} }
} }

View file

@ -56,6 +56,7 @@ import google.registry.util.Retrier;
import google.registry.util.TaskQueueUtils; import google.registry.util.TaskQueueUtils;
import google.registry.util.TeeOutputStream; import google.registry.util.TeeOutputStream;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
@ -103,11 +104,6 @@ public final class RdeUploadAction implements Runnable, EscrowTask {
@Inject JSchSshSessionFactory jschSshSessionFactory; @Inject JSchSshSessionFactory jschSshSessionFactory;
@Inject Response response; @Inject Response response;
@Inject RydePgpCompressionOutputStreamFactory pgpCompressionFactory;
@Inject RydePgpEncryptionOutputStreamFactory pgpEncryptionFactory;
@Inject RydePgpFileOutputStreamFactory pgpFileFactory;
@Inject RydePgpSigningOutputStreamFactory pgpSigningFactory;
@Inject RydeTarOutputStreamFactory tarFactory;
@Inject SftpProgressMonitor sftpProgressMonitor; @Inject SftpProgressMonitor sftpProgressMonitor;
@Inject TaskQueueUtils taskQueueUtils; @Inject TaskQueueUtils taskQueueUtils;
@Inject Retrier retrier; @Inject Retrier retrier;
@ -214,28 +210,27 @@ public final class RdeUploadAction implements Runnable, EscrowTask {
InputStream ghostrydeDecoder = Ghostryde.decoder(gcsInput, stagingDecryptionKey)) { InputStream ghostrydeDecoder = Ghostryde.decoder(gcsInput, stagingDecryptionKey)) {
try (JSchSshSession session = jschSshSessionFactory.create(lazyJsch.get(), uploadUrl); try (JSchSshSession session = jschSshSessionFactory.create(lazyJsch.get(), uploadUrl);
JSchSftpChannel ftpChan = session.openSftpChannel()) { JSchSftpChannel ftpChan = session.openSftpChannel()) {
byte[] signature; ByteArrayOutputStream sigOut = new ByteArrayOutputStream();
String rydeFilename = name + ".ryde"; String rydeFilename = name + ".ryde";
GcsFilename rydeGcsFilename = new GcsFilename(bucket, rydeFilename); GcsFilename rydeGcsFilename = new GcsFilename(bucket, rydeFilename);
try (OutputStream ftpOutput = try (OutputStream ftpOutput =
ftpChan.get().put(rydeFilename, sftpProgressMonitor, OVERWRITE); ftpChan.get().put(rydeFilename, sftpProgressMonitor, OVERWRITE);
OutputStream gcsOutput = gcsUtils.openOutputStream(rydeGcsFilename); OutputStream gcsOutput = gcsUtils.openOutputStream(rydeGcsFilename);
TeeOutputStream teeOutput = new TeeOutputStream(asList(ftpOutput, gcsOutput)); TeeOutputStream teeOutput = new TeeOutputStream(asList(ftpOutput, gcsOutput));
RydePgpSigningOutputStream signer = pgpSigningFactory.create(teeOutput, signingKey)) { RydeEncoder rydeEncoder =
try (OutputStream encryptLayer = pgpEncryptionFactory.create(signer, receiverKey); new RydeEncoder.Builder()
OutputStream kompressor = pgpCompressionFactory.create(encryptLayer); .setRydeOutput(teeOutput, receiverKey)
OutputStream fileLayer = pgpFileFactory.create(kompressor, watermark, name + ".tar"); .setSignatureOutput(sigOut, signingKey)
OutputStream tarLayer = .setFileMetadata(name, xmlLength, watermark)
tarFactory.create(fileLayer, xmlLength, watermark, name + ".xml")) { .build()) {
ByteStreams.copy(ghostrydeDecoder, tarLayer); 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"; String sigFilename = name + ".sig";
byte[] signature = sigOut.toByteArray();
gcsUtils.createFromBytes(new GcsFilename(bucket, sigFilename), signature); gcsUtils.createFromBytes(new GcsFilename(bucket, sigFilename), signature);
ftpChan.get().put(new ByteArrayInputStream(signature), sigFilename); 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);
} }
} }
} }

View file

@ -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.
*
* <p>The RyDE format has 2 files:
*
* <ul>
* <li>the "data" file, encoded in data -&gt; tar -&gt; PgpFile -&gt; compression -&gt; encryption
* <li>the signature of the resulting file
* </ul>
*
* <p>Hence, the encoder needs to receive 2 OutputStreams - one for the data and one for the
* signature.
*
* <p>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<PGPPublicKey> 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.
*
* <p>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<PGPPublicKey> 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<PGPPublicKey>().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'"));
}
}
}

View file

@ -16,9 +16,6 @@ package google.registry.rde;
import static org.bouncycastle.bcpg.CompressionAlgorithmTags.ZIP; 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 google.registry.util.ImprovedOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
@ -31,9 +28,10 @@ import org.bouncycastle.openpgp.PGPException;
* *
* <p>This uses the ZIP compression algorithm per the ICANN escrow specification. * <p>This uses the ZIP compression algorithm per the ICANN escrow specification.
*/ */
@AutoFactory(allowSubclasses = true)
public class RydePgpCompressionOutputStream extends ImprovedOutputStream { public class RydePgpCompressionOutputStream extends ImprovedOutputStream {
private static final int BUFFER_SIZE = 64 * 1024;
/** /**
* Creates a new instance that compresses data. * 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} * @throws RuntimeException to rethrow {@link PGPException} and {@link IOException}
*/ */
public RydePgpCompressionOutputStream( public RydePgpCompressionOutputStream(
@Provided @Config("rdeRydeBufferSize") Integer bufferSize,
@WillNotClose OutputStream os) { @WillNotClose OutputStream os) {
super("RydePgpCompressionOutputStream", createDelegate(bufferSize, os)); super("RydePgpCompressionOutputStream", createDelegate(BUFFER_SIZE, os));
} }
private static OutputStream createDelegate(int bufferSize, OutputStream os) { private static OutputStream createDelegate(int bufferSize, OutputStream os) {

View file

@ -14,18 +14,17 @@
package google.registry.rde; package google.registry.rde;
import static com.google.common.base.Preconditions.checkArgument;
import static org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags.AES_128; import static org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags.AES_128;
import static org.bouncycastle.jce.provider.BouncyCastleProvider.PROVIDER_NAME; 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 google.registry.util.ImprovedOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.ProviderException; import java.security.ProviderException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.Collection;
import javax.annotation.WillNotClose; import javax.annotation.WillNotClose;
import org.bouncycastle.openpgp.PGPEncryptedDataGenerator; import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;
import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPException;
@ -53,9 +52,10 @@ import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodG
* @see <a href="http://tools.ietf.org/html/rfc4880">RFC 4880 (OpenPGP Message Format)</a> * @see <a href="http://tools.ietf.org/html/rfc4880">RFC 4880 (OpenPGP Message Format)</a>
* @see <a href="http://en.wikipedia.org/wiki/Advanced_Encryption_Standard">AES (Wikipedia)</a> * @see <a href="http://en.wikipedia.org/wiki/Advanced_Encryption_Standard">AES (Wikipedia)</a>
*/ */
@AutoFactory(allowSubclasses = true)
public class RydePgpEncryptionOutputStream extends ImprovedOutputStream { 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 * 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. * 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} * @throws RuntimeException to rethrow {@link PGPException} and {@link IOException}
*/ */
public RydePgpEncryptionOutputStream( public RydePgpEncryptionOutputStream(
@Provided @Config("rdeRydeBufferSize") Integer bufferSize,
@WillNotClose OutputStream os, @WillNotClose OutputStream os,
PGPPublicKey receiverKey) { Collection<PGPPublicKey> receiverKeys) {
super("RydePgpEncryptionOutputStream", createDelegate(bufferSize, os, receiverKey)); super("RydePgpEncryptionOutputStream", createDelegate(os, receiverKeys));
} }
private static private static OutputStream createDelegate(
OutputStream createDelegate(int bufferSize, OutputStream os, PGPPublicKey receiverKey) { OutputStream os, Collection<PGPPublicKey> receiverKeys) {
try { try {
PGPEncryptedDataGenerator encryptor = new PGPEncryptedDataGenerator( PGPEncryptedDataGenerator encryptor = new PGPEncryptedDataGenerator(
new JcePGPDataEncryptorBuilder(CIPHER) new JcePGPDataEncryptorBuilder(CIPHER)
.setWithIntegrityPacket(USE_INTEGRITY_PACKET) .setWithIntegrityPacket(USE_INTEGRITY_PACKET)
.setSecureRandom(SecureRandom.getInstance(RANDOM_SOURCE)) .setSecureRandom(SecureRandom.getInstance(RANDOM_SOURCE))
.setProvider(PROVIDER_NAME)); .setProvider(PROVIDER_NAME));
encryptor.addMethod(new JcePublicKeyKeyEncryptionMethodGenerator(receiverKey)); checkArgument(!receiverKeys.isEmpty(), "Must give at least one receiver key");
return encryptor.open(os, new byte[bufferSize]); receiverKeys.forEach(
key -> encryptor.addMethod(new JcePublicKeyKeyEncryptionMethodGenerator(key)));
return encryptor.open(os, new byte[BUFFER_SIZE]);
} catch (NoSuchAlgorithmException e) { } catch (NoSuchAlgorithmException e) {
throw new ProviderException(e); throw new ProviderException(e);
} catch (IOException | PGPException e) { } catch (IOException | PGPException e) {

View file

@ -17,9 +17,6 @@ package google.registry.rde;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import static org.bouncycastle.openpgp.PGPLiteralData.BINARY; 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 google.registry.util.ImprovedOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
@ -36,9 +33,10 @@ import org.joda.time.DateTime;
* *
* <p>According to escrow spec, the PGP message should contain a single tar file. * <p>According to escrow spec, the PGP message should contain a single tar file.
*/ */
@AutoFactory(allowSubclasses = true)
public class RydePgpFileOutputStream extends ImprovedOutputStream { public class RydePgpFileOutputStream extends ImprovedOutputStream {
private static final int BUFFER_SIZE = 64 * 1024;
/** /**
* Creates a new instance for a particular file. * Creates a new instance for a particular file.
* *
@ -47,20 +45,19 @@ public class RydePgpFileOutputStream extends ImprovedOutputStream {
* @throws RuntimeException to rethrow {@link IOException} * @throws RuntimeException to rethrow {@link IOException}
*/ */
public RydePgpFileOutputStream( public RydePgpFileOutputStream(
@Provided @Config("rdeRydeBufferSize") Integer bufferSize,
@WillNotClose OutputStream os, @WillNotClose OutputStream os,
DateTime modified, DateTime modified,
String filename) { String filename) {
super("RydePgpFileOutputStream", createDelegate(bufferSize, os, modified, filename)); super("RydePgpFileOutputStream", createDelegate(os, modified, filename));
} }
private static OutputStream private static OutputStream
createDelegate(int bufferSize, OutputStream os, DateTime modified, String filename) { createDelegate(OutputStream os, DateTime modified, String filename) {
try { try {
checkArgument(filename.endsWith(".tar"), checkArgument(filename.endsWith(".tar"),
"Ryde PGP message should contain a tar file."); "Ryde PGP message should contain a tar file.");
return new PGPLiteralDataGenerator().open( return new PGPLiteralDataGenerator().open(
os, BINARY, filename, modified.toDate(), new byte[bufferSize]); os, BINARY, filename, modified.toDate(), new byte[BUFFER_SIZE]);
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }

View file

@ -18,7 +18,6 @@ import static org.bouncycastle.bcpg.HashAlgorithmTags.SHA256;
import static org.bouncycastle.bcpg.PublicKeyAlgorithmTags.RSA_GENERAL; import static org.bouncycastle.bcpg.PublicKeyAlgorithmTags.RSA_GENERAL;
import static org.bouncycastle.openpgp.PGPSignature.BINARY_DOCUMENT; import static org.bouncycastle.openpgp.PGPSignature.BINARY_DOCUMENT;
import com.google.auto.factory.AutoFactory;
import google.registry.util.ImprovedOutputStream; import google.registry.util.ImprovedOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; 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 * who receive a deposit to check the signature against our public key so they can know the
* data hasn't been forged. * data hasn't been forged.
*/ */
@AutoFactory(allowSubclasses = true)
public class RydePgpSigningOutputStream extends ImprovedOutputStream { public class RydePgpSigningOutputStream extends ImprovedOutputStream {
private final PGPSignatureGenerator signer; private final PGPSignatureGenerator signer;

View file

@ -16,7 +16,6 @@ package google.registry.rde;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import com.google.auto.factory.AutoFactory;
import google.registry.util.ImprovedOutputStream; import google.registry.util.ImprovedOutputStream;
import google.registry.util.PosixTarHeader; import google.registry.util.PosixTarHeader;
import java.io.IOException; import java.io.IOException;
@ -27,7 +26,6 @@ import org.joda.time.DateTime;
/** /**
* Single-file POSIX tar archive creator that wraps an {@link OutputStream}. * Single-file POSIX tar archive creator that wraps an {@link OutputStream}.
*/ */
@AutoFactory(allowSubclasses = true)
public class RydeTarOutputStream extends ImprovedOutputStream { public class RydeTarOutputStream extends ImprovedOutputStream {
private final long expectedSize; private final long expectedSize;

View file

@ -20,16 +20,7 @@ import com.google.common.io.ByteStreams;
import google.registry.keyring.api.KeyModule.Key; import google.registry.keyring.api.KeyModule.Key;
import google.registry.model.rde.RdeNamingUtils; import google.registry.model.rde.RdeNamingUtils;
import google.registry.rde.RdeUtil; import google.registry.rde.RdeUtil;
import google.registry.rde.RydePgpCompressionOutputStream; import google.registry.rde.RydeEncoder;
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.xml.XmlException; import google.registry.xml.XmlException;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.IOException; import java.io.IOException;
@ -40,7 +31,6 @@ import java.nio.file.Path;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Provider; import javax.inject.Provider;
import org.bouncycastle.bcpg.ArmoredOutputStream; import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPKeyPair; import org.bouncycastle.openpgp.PGPKeyPair;
import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPPublicKey;
import org.joda.time.DateTime; import org.joda.time.DateTime;
@ -50,18 +40,13 @@ final class EscrowDepositEncryptor {
private static final int PEEK_BUFFER_SIZE = 64 * 1024; 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<PGPKeyPair> rdeSigningKey; @Inject @Key("rdeSigningKey") Provider<PGPKeyPair> rdeSigningKey;
@Inject @Key("rdeReceiverKey") Provider<PGPPublicKey> rdeReceiverKey; @Inject @Key("rdeReceiverKey") Provider<PGPPublicKey> rdeReceiverKey;
@Inject EscrowDepositEncryptor() {} @Inject EscrowDepositEncryptor() {}
/** Creates a {@code .ryde} and {@code .sig} file, provided an XML deposit file. */ /** Creates a {@code .ryde} and {@code .sig} file, provided an XML deposit file. */
void encrypt(String tld, Path xmlFile, Path outdir) void encrypt(String tld, Path xmlFile, Path outdir)
throws IOException, PGPException, XmlException { throws IOException, XmlException {
try (InputStream xmlFileInput = Files.newInputStream(xmlFile); try (InputStream xmlFileInput = Files.newInputStream(xmlFile);
BufferedInputStream xmlInput = new BufferedInputStream(xmlFileInput, PEEK_BUFFER_SIZE)) { BufferedInputStream xmlInput = new BufferedInputStream(xmlFileInput, PEEK_BUFFER_SIZE)) {
DateTime watermark = RdeUtil.peekWatermark(xmlInput); DateTime watermark = RdeUtil.peekWatermark(xmlInput);
@ -71,19 +56,14 @@ final class EscrowDepositEncryptor {
Path pubPath = outdir.resolve(tld + ".pub"); Path pubPath = outdir.resolve(tld + ".pub");
PGPKeyPair signingKey = rdeSigningKey.get(); PGPKeyPair signingKey = rdeSigningKey.get();
try (OutputStream rydeOutput = Files.newOutputStream(rydePath); try (OutputStream rydeOutput = Files.newOutputStream(rydePath);
RydePgpSigningOutputStream signLayer = OutputStream sigOutput = Files.newOutputStream(sigPath);
pgpSigningFactory.create(rydeOutput, signingKey)) { RydeEncoder rydeEncoder = new RydeEncoder.Builder()
try (RydePgpEncryptionOutputStream encryptLayer = .setRydeOutput(rydeOutput, rdeReceiverKey.get())
pgpEncryptionFactory.create(signLayer, rdeReceiverKey.get()); .setSignatureOutput(sigOutput, signingKey)
RydePgpCompressionOutputStream compressLayer = .setFileMetadata(name, Files.size(xmlFile), watermark)
pgpCompressionFactory.create(encryptLayer); .build()) {
RydePgpFileOutputStream fileLayer = ByteStreams.copy(xmlInput, rydeEncoder);
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); try (OutputStream pubOutput = Files.newOutputStream(pubPath);
ArmoredOutputStream ascOutput = new ArmoredOutputStream(pubOutput)) { ArmoredOutputStream ascOutput = new ArmoredOutputStream(pubOutput)) {
signingKey.getPublicKey().encode(ascOutput); signingKey.getPublicKey().encode(ascOutput);
@ -91,4 +71,3 @@ final class EscrowDepositEncryptor {
} }
} }
} }
}

View file

@ -100,11 +100,6 @@ public class BrdaCopyActionTest extends ShardableTestCase {
@Before @Before
public void before() throws Exception { public void before() throws Exception {
action.gcsUtils = gcsUtils; 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.tld = "lol";
action.watermark = DateTime.parse("2010-10-17TZ"); action.watermark = DateTime.parse("2010-10-17TZ");
action.brdaBucket = "tub"; action.brdaBucket = "tub";

View file

@ -70,7 +70,6 @@ import google.registry.testing.FakeKeyringModule;
import google.registry.testing.FakeResponse; import google.registry.testing.FakeResponse;
import google.registry.testing.FakeSleeper; import google.registry.testing.FakeSleeper;
import google.registry.testing.GpgSystemCommandRule; import google.registry.testing.GpgSystemCommandRule;
import google.registry.testing.IoSpyRule;
import google.registry.testing.Lazies; import google.registry.testing.Lazies;
import google.registry.testing.TaskQueueHelper.TaskMatcher; import google.registry.testing.TaskQueueHelper.TaskMatcher;
import google.registry.testing.sftp.SftpServerRule; import google.registry.testing.sftp.SftpServerRule;
@ -81,10 +80,8 @@ import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket; import java.net.Socket;
import java.net.URI; import java.net.URI;
import org.bouncycastle.openpgp.PGPKeyPair;
import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPPublicKey;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.junit.Before; import org.junit.Before;
@ -131,11 +128,6 @@ public class RdeUploadActionTest {
RdeTestData.loadBytes("pgp-public-keyring.asc"), RdeTestData.loadBytes("pgp-public-keyring.asc"),
RdeTestData.loadBytes("pgp-private-keyring-escrow.asc")); RdeTestData.loadBytes("pgp-private-keyring-escrow.asc"));
@Rule
public final IoSpyRule ioSpy = new IoSpyRule()
.checkClosedOnlyOnce()
.checkCharIoMaxCalls(10);
@Rule @Rule
public final AppEngineRule appEngine = AppEngineRule.builder() public final AppEngineRule appEngine = AppEngineRule.builder()
.withDatastore() .withDatastore()
@ -146,42 +138,6 @@ public class RdeUploadActionTest {
private final EscrowTaskRunner runner = mock(EscrowTaskRunner.class); private final EscrowTaskRunner runner = mock(EscrowTaskRunner.class);
private final FakeClock clock = new FakeClock(DateTime.parse("2010-10-17TZ")); 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) { private RdeUploadAction createAction(URI uploadUrl) {
try (Keyring keyring = new FakeKeyringModule().get()) { try (Keyring keyring = new FakeKeyringModule().get()) {
RdeUploadAction action = new RdeUploadAction(); RdeUploadAction action = new RdeUploadAction();
@ -195,11 +151,6 @@ public class RdeUploadActionTest {
keyring.getRdeSshClientPublicKey()); keyring.getRdeSshClientPublicKey());
action.jschSshSessionFactory = new JSchSshSessionFactory(standardSeconds(3)); action.jschSshSessionFactory = new JSchSshSessionFactory(standardSeconds(3));
action.response = response; action.response = response;
action.pgpCompressionFactory = compressFactory;
action.pgpEncryptionFactory = encryptFactory;
action.pgpFileFactory = literalFactory;
action.pgpSigningFactory = signFactory;
action.tarFactory = tarFactory;
action.bucket = "bucket"; action.bucket = "bucket";
action.interval = standardDays(1); action.interval = standardDays(1);
action.timeout = standardSeconds(23); action.timeout = standardSeconds(23);

View file

@ -68,12 +68,6 @@ public class RydeGpgIntegrationTest extends ShardableTestCase {
new GpgCommand("gpg2"), new GpgCommand("gpg2"),
}; };
@DataPoints
public static BufferSize[] bufferSizes = new BufferSize[] {
new BufferSize(1),
new BufferSize(7),
};
@DataPoints @DataPoints
public static Filename[] filenames = new Filename[] { public static Filename[] filenames = new Filename[] {
new Filename("sloth"), new Filename("sloth"),
@ -88,22 +82,11 @@ public class RydeGpgIntegrationTest extends ShardableTestCase {
}; };
@Theory @Theory
public void test(GpgCommand cmd, BufferSize bufSize, Filename name, Content content) public void test(GpgCommand cmd, Filename name, Content content)
throws Exception { throws Exception {
assumeTrue(hasCommand("tar")); assumeTrue(hasCommand("tar"));
assumeTrue(hasCommand(cmd.get() + " --version")); 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(); Keyring keyring = keyringFactory.get();
PGPKeyPair signingKey = keyring.getRdeSigningKey(); PGPKeyPair signingKey = keyring.getRdeSigningKey();
PGPPublicKey receiverKey = keyring.getRdeReceiverKey(); PGPPublicKey receiverKey = keyring.getRdeReceiverKey();
@ -116,20 +99,13 @@ public class RydeGpgIntegrationTest extends ShardableTestCase {
byte[] data = content.get().getBytes(UTF_8); byte[] data = content.get().getBytes(UTF_8);
try (OutputStream rydeOut = new FileOutputStream(rydeFile); try (OutputStream rydeOut = new FileOutputStream(rydeFile);
RydePgpSigningOutputStream signLayer = pgpSigningFactory.create(rydeOut, signingKey)) { OutputStream sigOut = new FileOutputStream(sigFile);
try (RydePgpEncryptionOutputStream encryptLayer = RydeEncoder rydeEncoder = new RydeEncoder.Builder()
pgpEncryptionFactory.create(signLayer, receiverKey); .setRydeOutput(rydeOut, receiverKey)
RydePgpCompressionOutputStream compressLayer = .setSignatureOutput(sigOut, signingKey)
pgpCompressionFactory.create(encryptLayer); .setFileMetadata(name.get(), data.length, modified)
RydePgpFileOutputStream fileLayer = .build()) {
pgpFileFactory.create(compressLayer, modified, name.get() + ".tar"); rydeEncoder.write(data);
RydeTarOutputStream tarLayer =
tarFactory.create(fileLayer, data.length, modified, name.get() + ".xml")) {
tarLayer.write(data);
}
try (OutputStream sigOut = new FileOutputStream(sigFile)) {
sigOut.write(signLayer.getSignature());
}
} }
// Iron Mountain examines the ryde file to see what sort of OpenPGP layers it contains. // 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 static class Filename {
private final String value; private final String value;

View file

@ -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<Closeable> spiedCloseables = new ArrayList<>();
private final List<InputStream> spiedInputStreams = new ArrayList<>();
private final List<OutputStream> spiedOutputStreams = new ArrayList<>();
public IoSpyRule() {}
/**
* Enables check where {@link Closeable#close() close} must be called EXACTLY once.
*
* <p>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 extends Closeable> 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());
}
}
}
}

View file

@ -20,11 +20,6 @@ import static google.registry.testing.TestDataHelper.loadBytes;
import com.google.common.io.ByteSource; import com.google.common.io.ByteSource;
import com.google.common.io.Files; import com.google.common.io.Files;
import google.registry.rde.RdeTestData; 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.BouncyCastleProviderRule;
import google.registry.testing.FakeKeyringModule; import google.registry.testing.FakeKeyringModule;
import java.io.File; import java.io.File;
@ -43,11 +38,6 @@ public class EncryptEscrowDepositCommandTest
static EscrowDepositEncryptor createEncryptor() { static EscrowDepositEncryptor createEncryptor() {
EscrowDepositEncryptor res = new EscrowDepositEncryptor(); 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.rdeReceiverKey = () -> new FakeKeyringModule().get().getRdeReceiverKey();
res.rdeSigningKey = () -> new FakeKeyringModule().get().getRdeSigningKey(); res.rdeSigningKey = () -> new FakeKeyringModule().get().getRdeSigningKey();
return res; return res;