mirror of
https://github.com/google/nomulus.git
synced 2025-05-05 14:37:52 +02:00
Merges the encryptor creation between RyDE and Ghostryde. The new file - RydeEncryption.java - is a merge of the removed functions in Ghostryde.java and the RydePgpEncryptionOutputStream.java. This is one of a series of CLs - each merging a single "part" of the encoding. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=205246053
286 lines
11 KiB
Java
286 lines
11 KiB
Java
// 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.rde;
|
|
|
|
import static com.google.common.base.Strings.repeat;
|
|
import static com.google.common.truth.Truth.assertThat;
|
|
import static google.registry.keyring.api.PgpHelper.KeyRequirement.ENCRYPT;
|
|
import static google.registry.testing.JUnitBackports.assertThrows;
|
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
|
import static org.hamcrest.Matchers.greaterThan;
|
|
import static org.hamcrest.Matchers.is;
|
|
import static org.hamcrest.Matchers.lessThan;
|
|
import static org.junit.Assume.assumeThat;
|
|
|
|
import com.google.common.io.ByteStreams;
|
|
import google.registry.keyring.api.Keyring;
|
|
import google.registry.testing.BouncyCastleProviderRule;
|
|
import google.registry.testing.FakeKeyringModule;
|
|
import java.io.ByteArrayInputStream;
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.io.InputStream;
|
|
import java.io.OutputStream;
|
|
import java.util.Base64;
|
|
import org.bouncycastle.openpgp.PGPException;
|
|
import org.bouncycastle.openpgp.PGPKeyPair;
|
|
import org.bouncycastle.openpgp.PGPPrivateKey;
|
|
import org.bouncycastle.openpgp.PGPPublicKey;
|
|
import org.junit.Ignore;
|
|
import org.junit.Rule;
|
|
import org.junit.Test;
|
|
import org.junit.experimental.theories.DataPoints;
|
|
import org.junit.experimental.theories.Theories;
|
|
import org.junit.experimental.theories.Theory;
|
|
import org.junit.runner.RunWith;
|
|
|
|
/** Unit tests for {@link Ghostryde}. */
|
|
@RunWith(Theories.class)
|
|
@SuppressWarnings("resource")
|
|
public class GhostrydeTest {
|
|
|
|
@Rule
|
|
public final BouncyCastleProviderRule bouncy = new BouncyCastleProviderRule();
|
|
|
|
@DataPoints
|
|
public static Content[] contents = new Content[] {
|
|
new Content("hi"),
|
|
new Content("(◕‿◕)"),
|
|
new Content(repeat("Fanatics have their dreams, wherewith they weave.\n", 1000)),
|
|
new Content("\0yolo"),
|
|
new Content(""),
|
|
};
|
|
|
|
@Theory
|
|
public void testSimpleApi(Content content) throws Exception {
|
|
Keyring keyring = new FakeKeyringModule().get();
|
|
byte[] data = content.get().getBytes(UTF_8);
|
|
PGPPublicKey publicKey = keyring.getRdeStagingEncryptionKey();
|
|
PGPPrivateKey privateKey = keyring.getRdeStagingDecryptionKey();
|
|
|
|
byte[] blob = Ghostryde.encode(data, publicKey);
|
|
byte[] result = Ghostryde.decode(blob, privateKey);
|
|
|
|
assertThat(new String(result, UTF_8)).isEqualTo(content.get());
|
|
}
|
|
|
|
@Theory
|
|
public void testStreamingApi(Content content) throws Exception {
|
|
Keyring keyring = new FakeKeyringModule().get();
|
|
byte[] data = content.get().getBytes(UTF_8);
|
|
PGPPublicKey publicKey = keyring.getRdeStagingEncryptionKey();
|
|
PGPPrivateKey privateKey = keyring.getRdeStagingDecryptionKey();
|
|
|
|
ByteArrayOutputStream bsOut = new ByteArrayOutputStream();
|
|
try (OutputStream encoder = Ghostryde.encoder(bsOut, publicKey)) {
|
|
encoder.write(data);
|
|
}
|
|
|
|
ByteArrayInputStream bsIn = new ByteArrayInputStream(bsOut.toByteArray());
|
|
bsOut.reset();
|
|
try (InputStream decoder = Ghostryde.decoder(bsIn, privateKey)) {
|
|
ByteStreams.copy(decoder, bsOut);
|
|
}
|
|
assertThat(bsOut.size()).isEqualTo(data.length);
|
|
|
|
assertThat(new String(bsOut.toByteArray(), UTF_8)).isEqualTo(content.get());
|
|
}
|
|
|
|
@Theory
|
|
public void testStreamingApi_withSize(Content content) throws Exception {
|
|
Keyring keyring = new FakeKeyringModule().get();
|
|
byte[] data = content.get().getBytes(UTF_8);
|
|
PGPPublicKey publicKey = keyring.getRdeStagingEncryptionKey();
|
|
|
|
ByteArrayOutputStream bsOut = new ByteArrayOutputStream();
|
|
ByteArrayOutputStream lenOut = new ByteArrayOutputStream();
|
|
try (OutputStream encoder = Ghostryde.encoder(bsOut, publicKey, lenOut)) {
|
|
encoder.write(data);
|
|
}
|
|
|
|
assertThat(Ghostryde.readLength(new ByteArrayInputStream(lenOut.toByteArray())))
|
|
.isEqualTo(data.length);
|
|
assertThat(Long.parseLong(new String(lenOut.toByteArray(), UTF_8))).isEqualTo(data.length);
|
|
}
|
|
|
|
@Theory
|
|
public void testFailure_tampering(Content content) throws Exception {
|
|
assumeThat(content.get().length(), is(greaterThan(100)));
|
|
|
|
Keyring keyring = new FakeKeyringModule().get();
|
|
PGPPublicKey publicKey = keyring.getRdeStagingEncryptionKey();
|
|
PGPPrivateKey privateKey = keyring.getRdeStagingDecryptionKey();
|
|
byte[] data = content.get().getBytes(UTF_8);
|
|
|
|
ByteArrayOutputStream bsOut = new ByteArrayOutputStream();
|
|
try (OutputStream encoder = Ghostryde.encoder(bsOut, publicKey)) {
|
|
encoder.write(data);
|
|
}
|
|
|
|
byte[] ciphertext = bsOut.toByteArray();
|
|
korruption(ciphertext, ciphertext.length - 1);
|
|
|
|
ByteArrayInputStream bsIn = new ByteArrayInputStream(ciphertext);
|
|
IllegalStateException thrown =
|
|
assertThrows(
|
|
IllegalStateException.class,
|
|
() -> {
|
|
try (InputStream decoder = Ghostryde.decoder(bsIn, privateKey)) {
|
|
ByteStreams.copy(decoder, ByteStreams.nullOutputStream());
|
|
}
|
|
});
|
|
assertThat(thrown).hasMessageThat().contains("tampering");
|
|
}
|
|
|
|
@Theory
|
|
public void testFailure_corruption(Content content) throws Exception {
|
|
assumeThat(content.get().length(), is(lessThan(100)));
|
|
|
|
Keyring keyring = new FakeKeyringModule().get();
|
|
PGPPublicKey publicKey = keyring.getRdeStagingEncryptionKey();
|
|
PGPPrivateKey privateKey = keyring.getRdeStagingDecryptionKey();
|
|
byte[] data = content.get().getBytes(UTF_8);
|
|
|
|
ByteArrayOutputStream bsOut = new ByteArrayOutputStream();
|
|
try (OutputStream encoder = Ghostryde.encoder(bsOut, publicKey)) {
|
|
encoder.write(data);
|
|
}
|
|
|
|
byte[] ciphertext = bsOut.toByteArray();
|
|
korruption(ciphertext, ciphertext.length / 2);
|
|
|
|
ByteArrayInputStream bsIn = new ByteArrayInputStream(ciphertext);
|
|
RuntimeException thrown =
|
|
assertThrows(
|
|
RuntimeException.class,
|
|
() -> {
|
|
try (InputStream decoder = Ghostryde.decoder(bsIn, privateKey)) {
|
|
ByteStreams.copy(decoder, ByteStreams.nullOutputStream());
|
|
}
|
|
});
|
|
assertThat(thrown).hasCauseThat().isInstanceOf(PGPException.class);
|
|
}
|
|
|
|
@Test
|
|
public void testFullEncryption() throws Exception {
|
|
// Check that the full encryption hasn't changed. All the other tests check that encrypting and
|
|
// decrypting results in the original data, but not whether the encryption method has changed.
|
|
FakeKeyringModule keyringModule = new FakeKeyringModule();
|
|
PGPKeyPair dsa = keyringModule.get("rde-unittest@registry.test", ENCRYPT);
|
|
PGPPrivateKey privateKey = dsa.getPrivateKey();
|
|
|
|
// Encryption is inconsistent because it uses a random state. But decryption is consistent!
|
|
//
|
|
// If the encryption has legitimately changed - uncomment the following code, and copy the new
|
|
// encryptedInputBase64 from the test error:
|
|
//
|
|
// assertThat(
|
|
// Base64.getMimeEncoder()
|
|
// .encodeToString(
|
|
// Ghostryde.encode("Some data!!!111!!!".getBytes(UTF_8), dsa.getPublicKey())))
|
|
// .isEqualTo("expect error");
|
|
|
|
String encryptedInputBase64 =
|
|
" hQEMA6WcEy81iaHVAQgAnn9bS6IOCTW2uZnITPWH8zIYr6K7YJslv38c4YU5eQqVhHC5PN0NhM2l\n"
|
|
+ " i89U3lUE6gp3DdEEbTbugwXCHWyRL4fYTlpiHZjBn2vZdSS21EAG+q1XuTaD8DTjkC2G060/sW6i\n"
|
|
+ " 0gSIkksqgubbSVZTxHEqh92tv35KCqiYc52hjKZIIGI8FHhpJOtDa3bhMMad8nrMy3vbv5LiYNh5\n"
|
|
+ " j3DUCFhskU8Ldi1vBfXIonqUNLBrD/R471VVJyQ3NoGQTVUF9uXLoy+2dL0oBLc1Avj1XNP5PQ08\n"
|
|
+ " MWlqmezkLdY0oHnQqTHYhYDxRo/Sw7xO1GLwWR11rcx/IAJloJbKSHTFeNJUAcKFnKvPDwBk3nnr\n"
|
|
+ " uR505HtOj/tZDT5weVjhrlnmWXzaBRmYASy6PXZu6KzTbPUQTf4JeeJWdyw7glLMr2WPdMVPGZ8e\n"
|
|
+ " gcFAjSJZjZlqohZyBUpP\n";
|
|
|
|
byte[] result =
|
|
Ghostryde.decode(Base64.getMimeDecoder().decode(encryptedInputBase64), privateKey);
|
|
|
|
assertThat(new String(result, UTF_8)).isEqualTo("Some data!!!111!!!");
|
|
}
|
|
|
|
@Test
|
|
public void testFailure_keyMismatch() throws Exception {
|
|
FakeKeyringModule keyringModule = new FakeKeyringModule();
|
|
byte[] data = "Fanatics have their dreams, wherewith they weave.".getBytes(UTF_8);
|
|
PGPKeyPair dsa1 = keyringModule.get("rde-unittest@registry.test", ENCRYPT);
|
|
PGPKeyPair dsa2 = keyringModule.get("rde-unittest-dsa@registry.test", ENCRYPT);
|
|
PGPPublicKey publicKey = dsa1.getPublicKey();
|
|
PGPPrivateKey privateKey = dsa2.getPrivateKey();
|
|
|
|
ByteArrayOutputStream bsOut = new ByteArrayOutputStream();
|
|
try (OutputStream encoder = Ghostryde.encoder(bsOut, publicKey)) {
|
|
encoder.write(data);
|
|
}
|
|
|
|
ByteArrayInputStream bsIn = new ByteArrayInputStream(bsOut.toByteArray());
|
|
RuntimeException thrown =
|
|
assertThrows(
|
|
RuntimeException.class,
|
|
() -> {
|
|
try (InputStream decoder = Ghostryde.decoder(bsIn, privateKey)) {
|
|
ByteStreams.copy(decoder, ByteStreams.nullOutputStream());
|
|
}
|
|
});
|
|
assertThat(thrown).hasCauseThat().isInstanceOf(PGPException.class);
|
|
assertThat(thrown)
|
|
.hasCauseThat()
|
|
.hasMessageThat()
|
|
.contains(
|
|
"Message was encrypted for keyids [a59c132f3589a1d5] but ours is c9598c84ec70b9fd");
|
|
}
|
|
|
|
@Test
|
|
@Ignore("Intentionally corrupting a PGP key is easier said than done >_>")
|
|
public void testFailure_keyCorruption() throws Exception {
|
|
FakeKeyringModule keyringModule = new FakeKeyringModule();
|
|
byte[] data = "Fanatics have their dreams, wherewith they weave.".getBytes(UTF_8);
|
|
PGPKeyPair rsa = keyringModule.get("rde-unittest@registry.test", ENCRYPT);
|
|
PGPPublicKey publicKey = rsa.getPublicKey();
|
|
|
|
// Make the last byte of the private key off by one. muahahaha
|
|
byte[] keyData = rsa.getPrivateKey().getPrivateKeyDataPacket().getEncoded();
|
|
keyData[keyData.length - 1]++;
|
|
PGPPrivateKey privateKey = new PGPPrivateKey(
|
|
rsa.getKeyID(),
|
|
rsa.getPrivateKey().getPublicKeyPacket(),
|
|
rsa.getPrivateKey().getPrivateKeyDataPacket());
|
|
|
|
ByteArrayOutputStream bsOut = new ByteArrayOutputStream();
|
|
try (OutputStream encoder = Ghostryde.encoder(bsOut, publicKey)) {
|
|
encoder.write(data);
|
|
}
|
|
|
|
ByteArrayInputStream bsIn = new ByteArrayInputStream(bsOut.toByteArray());
|
|
try (InputStream decoder = Ghostryde.decoder(bsIn, privateKey)) {
|
|
ByteStreams.copy(decoder, ByteStreams.nullOutputStream());
|
|
}
|
|
}
|
|
|
|
private void korruption(byte[] bytes, int position) {
|
|
if (bytes[position] == 23) {
|
|
bytes[position] = 7;
|
|
} else {
|
|
bytes[position] = 23;
|
|
}
|
|
}
|
|
|
|
private static class Content {
|
|
private final String value;
|
|
|
|
Content(String value) {
|
|
this.value = value;
|
|
}
|
|
|
|
String get() {
|
|
return value;
|
|
}
|
|
}
|
|
}
|