mirror of
https://github.com/google/nomulus.git
synced 2025-05-14 08:27:14 +02:00
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:
parent
c4a2b5fa8d
commit
8ec2eaf39c
15 changed files with 215 additions and 345 deletions
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
signature = signer.getSignature();
|
||||
logger.atInfo().log("uploaded %,d bytes: %s.ryde", signer.getBytesWritten(), name);
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
153
java/google/registry/rde/RydeEncoder.java
Normal file
153
java/google/registry/rde/RydeEncoder.java
Normal 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 -> tar -> PgpFile -> compression -> 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'"));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
|||
*
|
||||
* <p>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) {
|
||||
|
|
|
@ -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 <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>
|
||||
*/
|
||||
@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<PGPPublicKey> receiverKeys) {
|
||||
super("RydePgpEncryptionOutputStream", createDelegate(os, receiverKeys));
|
||||
}
|
||||
|
||||
private static
|
||||
OutputStream createDelegate(int bufferSize, OutputStream os, PGPPublicKey receiverKey) {
|
||||
private static OutputStream createDelegate(
|
||||
OutputStream os, Collection<PGPPublicKey> 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) {
|
||||
|
|
|
@ -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;
|
|||
*
|
||||
* <p>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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<PGPKeyPair> rdeSigningKey;
|
||||
@Inject @Key("rdeReceiverKey") Provider<PGPPublicKey> 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,19 +56,14 @@ 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);
|
||||
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);
|
||||
}
|
||||
Files.write(sigPath, signLayer.getSignature());
|
||||
try (OutputStream pubOutput = Files.newOutputStream(pubPath);
|
||||
ArmoredOutputStream ascOutput = new ArmoredOutputStream(pubOutput)) {
|
||||
signingKey.getPublicKey().encode(ascOutput);
|
||||
|
@ -91,4 +71,3 @@ final class EscrowDepositEncryptor {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue