diff --git a/java/google/registry/request/lock/BUILD b/java/google/registry/request/lock/BUILD new file mode 100644 index 000000000..7e69134ca --- /dev/null +++ b/java/google/registry/request/lock/BUILD @@ -0,0 +1,16 @@ +package( + default_visibility = ["//visibility:public"], +) + +licenses(["notice"]) # Apache 2.0 + +java_library( + name = "lock", + srcs = glob(["*.java"]), + deps = [ + "//java/google/registry/model", + "@com_google_code_findbugs_jsr305", + "@com_google_dagger", + "@joda_time", + ], +) diff --git a/java/google/registry/request/lock/LockHandler.java b/java/google/registry/request/lock/LockHandler.java new file mode 100644 index 000000000..897bef899 --- /dev/null +++ b/java/google/registry/request/lock/LockHandler.java @@ -0,0 +1,45 @@ +// 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.request.lock; + +import java.io.Serializable; +import java.util.concurrent.Callable; +import javax.annotation.Nullable; +import org.joda.time.Duration; + +/** + * Code execution locked on some shared resource. + * + *

Locks are either specific to a tld or global to the entire system, in which case a tld of + * null is used. + */ +public interface LockHandler extends Serializable { + + /** + * Acquire one or more locks and execute a Void {@link Callable}. + * + *

Runs on a thread that will be killed if it doesn't complete before the lease expires. + * + *

Note that locks are specific either to a given tld or to the entire system (in which case + * tld should be passed as null). + * + * @return true if all locks were acquired and the callable was run; false otherwise. + */ + public boolean executeWithLocks( + final Callable callable, + @Nullable String tld, + Duration leaseLength, + String... lockNames); +} diff --git a/java/google/registry/request/lock/LockHandlerPassthrough.java b/java/google/registry/request/lock/LockHandlerPassthrough.java new file mode 100644 index 000000000..0d0a68f76 --- /dev/null +++ b/java/google/registry/request/lock/LockHandlerPassthrough.java @@ -0,0 +1,54 @@ +// 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.request.lock; + +import google.registry.model.server.Lock; +import java.util.concurrent.Callable; +import javax.annotation.Nullable; +import javax.inject.Inject; +import org.joda.time.Duration; + +/** + * Implementation of {@link LockHandler} that uses Lock as is. + * + * This is a temporary implementation to help migrate from Lock to LockHandler. Once the migration + * is complete - we will create a "proper" LockHandlerImpl class and remove this one. + * + * TODO(guyben):delete this class once LockHandlerImpl is done. + */ +public class LockHandlerPassthrough implements LockHandler { + + private static final long serialVersionUID = 6551645164118637767L; + + @Inject public LockHandlerPassthrough() {} + + /** + * Acquire one or more locks and execute a Void {@link Callable}. + * + *

Runs on a thread that will be killed if it doesn't complete before the lease expires. + * + *

This is a simple passthrough to {@link Lock#executeWithLocks}. + * + * @return true if all locks were acquired and the callable was run; false otherwise. + */ + @Override + public boolean executeWithLocks( + final Callable callable, + @Nullable String tld, + Duration leaseLength, + String... lockNames) { + return Lock.executeWithLocks(callable, tld, leaseLength, lockNames); + } +} diff --git a/javatests/google/registry/keyring/api/BUILD b/javatests/google/registry/keyring/api/BUILD new file mode 100644 index 000000000..87ad4a97f --- /dev/null +++ b/javatests/google/registry/keyring/api/BUILD @@ -0,0 +1,30 @@ +package( + default_testonly = 1, + default_visibility = ["//java/google/registry:registry_project"], +) + +licenses(["notice"]) # Apache 2.0 + +load("//java/com/google/testing/builddefs:GenTestRules.bzl", "GenTestRules") + +java_library( + name = "api", + srcs = glob(["*.java"]), + deps = [ + "//java/google/registry/keyring/api", + "//javatests/google/registry/testing", + "@com_google_guava_testlib", + "@com_google_truth", + "@junit", + "@org_bouncycastle_bcpg_jdk15on", + "@org_bouncycastle_bcpkix_jdk15on", + "@org_hamcrest_library", + "@org_mockito_all", + ], +) + +GenTestRules( + name = "GeneratedTestRules", + test_files = glob(["*Test.java"]), + deps = [":api"], +) diff --git a/javatests/google/registry/keyring/api/ComparatorKeyringTest.java b/javatests/google/registry/keyring/api/ComparatorKeyringTest.java new file mode 100644 index 000000000..db12dd9e8 --- /dev/null +++ b/javatests/google/registry/keyring/api/ComparatorKeyringTest.java @@ -0,0 +1,345 @@ +// 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.keyring.api; + +import static com.google.common.truth.Truth.assertThat; +import static google.registry.testing.LogsSubject.assertAboutLogs; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.google.common.testing.TestLogHandler; +import java.io.IOException; +import java.util.logging.Level; +import org.bouncycastle.bcpg.BCPGKey; +import org.bouncycastle.bcpg.PublicKeyPacket; +import org.bouncycastle.openpgp.PGPKeyPair; +import org.bouncycastle.openpgp.PGPPrivateKey; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link ComparatorKeyring}. */ +@RunWith(JUnit4.class) +public class ComparatorKeyringTest { + + private static final String PUBLIC_KEY_FINGERPRINT = "fingerprint"; + private static final String PUBLIC_KEY_TO_STRING = + "PGPPublicKey{fingerprint=66:69:6e:67:65:72:70:72:69:6e:74:}"; + private static final String PRIVATE_KEY_TO_STRING = + "PGPPrivateKey{keyId=1}"; + private static final String KEY_PAIR_TO_STRING = + String.format("PGPKeyPair{%s, %s}", PUBLIC_KEY_TO_STRING, PRIVATE_KEY_TO_STRING); + + private static PGPPublicKey mockPublicKey( + boolean altFingerprint, + boolean altEncoded) throws IOException { + PGPPublicKey publicKey = mock(PGPPublicKey.class); + String fingerprint = altFingerprint ? "alternate" : PUBLIC_KEY_FINGERPRINT; + String encoded = altEncoded ? "alternate" : "publicKeyEncoded"; + when(publicKey.getFingerprint()).thenReturn(fingerprint.getBytes(UTF_8)); + when(publicKey.getEncoded()).thenReturn(encoded.getBytes(UTF_8)); + return publicKey; + } + + private static PGPPrivateKey mockPrivateKey( + boolean altId, + boolean altBcpgKeyFormat, + boolean altBcpgKeyEncoded, + boolean altPublicKeyPacketEncoded) + throws IOException { + String bcpgKeyFormat = altBcpgKeyFormat ? "alternate" : "bcpgFormat"; + String bcpgKeyEncoded = altBcpgKeyEncoded ? "alternate" : "bcpgEncoded"; + String publicKeyPacketEncoded = altPublicKeyPacketEncoded ? "alternate" : "packetEncoded"; + + BCPGKey bcpgKey = mock(BCPGKey.class); + PublicKeyPacket publicKeyPacket = mock(PublicKeyPacket.class); + when(bcpgKey.getFormat()).thenReturn(bcpgKeyFormat); + when(bcpgKey.getEncoded()).thenReturn(bcpgKeyEncoded.getBytes(UTF_8)); + when(publicKeyPacket.getEncoded()).thenReturn(publicKeyPacketEncoded.getBytes(UTF_8)); + return new PGPPrivateKey(altId ? 2 : 1, publicKeyPacket, bcpgKey); + } + + private final TestLogHandler testLogHandler = new TestLogHandler(); + + @Before + public void setUp() { + ComparatorKeyring.logger.addHandler(testLogHandler); + } + + @After + public void tearDown() { + ComparatorKeyring.logger.removeHandler(testLogHandler); + } + + @Test + public void testPublicKeyToString() throws Exception { + assertThat( + ComparatorKeyring.stringify( + mockPublicKey(false, false))) + .isEqualTo(PUBLIC_KEY_TO_STRING); + } + + @Test + public void testPublicKeyEquals() throws Exception { + assertThat( + ComparatorKeyring.compare( + mockPublicKey(false, false), + mockPublicKey(false, false))) + .isTrue(); + } + + @Test + public void testPublicKeyDifferFingerprint_notEqual() throws Exception { + assertThat( + ComparatorKeyring.compare( + mockPublicKey(false, false), + mockPublicKey(true, false))) + .isFalse(); + } + + @Test + public void testPublicKeyDifferEncoded_notEqual() throws Exception { + assertThat( + ComparatorKeyring.compare( + mockPublicKey(false, false), + mockPublicKey(false, true))) + .isFalse(); + } + + @Test + public void testPrivateKeyToString() throws Exception { + assertThat( + ComparatorKeyring.stringify( + mockPrivateKey(false, false, false, false))) + .isEqualTo(PRIVATE_KEY_TO_STRING); + } + + @Test + public void testPrivateKeyEquals() throws Exception { + assertThat( + ComparatorKeyring.compare( + mockPrivateKey(false, false, false, false), + mockPrivateKey(false, false, false, false))) + .isTrue(); + } + + @Test + public void testPrivateKeyDifferId_notEquals() throws Exception { + assertThat( + ComparatorKeyring.compare( + mockPrivateKey(false, false, false, false), + mockPrivateKey(true, false, false, false))) + .isFalse(); + } + + @Test + public void testPrivateKeyDifferBcpgFormat_notEquals() throws Exception { + assertThat( + ComparatorKeyring.compare( + mockPrivateKey(false, false, false, false), + mockPrivateKey(false, true, false, false))) + .isFalse(); + } + + @Test + public void testPrivateKeyDifferBcpgEncoding_notEquals() throws Exception { + assertThat( + ComparatorKeyring.compare( + mockPrivateKey(false, false, false, false), + mockPrivateKey(false, false, true, false))) + .isFalse(); + } + + @Test + public void testPrivateKeyDifferPublicKeyEncoding_notEquals() throws Exception { + assertThat( + ComparatorKeyring.compare( + mockPrivateKey(false, false, false, false), + mockPrivateKey(false, false, false, true))) + .isFalse(); + } + + @Test + public void testKeyPairToString() throws Exception { + assertThat( + ComparatorKeyring.stringify( + new PGPKeyPair( + mockPublicKey(false, false), + mockPrivateKey(false, false, false, false)))) + .isEqualTo(KEY_PAIR_TO_STRING); + } + + @Test + public void testKeyPairEquals() throws Exception { + assertThat( + ComparatorKeyring.compare( + new PGPKeyPair( + mockPublicKey(false, false), + mockPrivateKey(false, false, false, false)), + new PGPKeyPair( + mockPublicKey(false, false), + mockPrivateKey(false, false, false, false)))) + .isTrue(); + } + + @Test + public void testKeyPairDifferPublicKey_notEqual() throws Exception { + assertThat( + ComparatorKeyring.compare( + new PGPKeyPair( + mockPublicKey(false, false), + mockPrivateKey(false, false, false, false)), + new PGPKeyPair( + mockPublicKey(true, false), + mockPrivateKey(false, false, false, false)))) + .isFalse(); + } + + @Test + public void testKeyPairDifferPrivateKey_notEqual() throws Exception { + assertThat( + ComparatorKeyring.compare( + new PGPKeyPair( + mockPublicKey(false, false), + mockPrivateKey(false, false, false, false)), + new PGPKeyPair( + mockPublicKey(false, false), + mockPrivateKey(true, false, false, false)))) + .isFalse(); + } + + // We don't need to check every single method in the generated instance to see that it behaves + // correctly. This should have been tested in ComparatorGenerator. + // + // We will fully test a single method just to make sure everything is "connected" correctly. + + @Test + public void testRdeSigningKey_actualThrows() throws Exception { + Keyring actualKeyring = mock(Keyring.class); + Keyring secondKeyring = mock(Keyring.class); + PGPKeyPair keyPair = + new PGPKeyPair( + mockPublicKey(false, false), + mockPrivateKey(false, false, false, false)); + when(actualKeyring.getRdeSigningKey()).thenThrow(new KeyringException("message")); + when(secondKeyring.getRdeSigningKey()).thenReturn(keyPair); + Keyring comparatorKeyring = ComparatorKeyring.create(actualKeyring, secondKeyring); + + try { + comparatorKeyring.getRdeSigningKey(); + fail("Should have thrown KeyringException"); + } catch (KeyringException expected) { + } + + assertAboutLogs() + .that(testLogHandler) + .hasLogAtLevelWithMessage( + Level.SEVERE, ".getRdeSigningKey: Only actual implementation threw exception"); + } + + @Test + public void testRdeSigningKey_secondThrows() throws Exception { + Keyring actualKeyring = mock(Keyring.class); + Keyring secondKeyring = mock(Keyring.class); + PGPKeyPair keyPair = + new PGPKeyPair( + mockPublicKey(false, false), + mockPrivateKey(false, false, false, false)); + when(actualKeyring.getRdeSigningKey()).thenReturn(keyPair); + when(secondKeyring.getRdeSigningKey()).thenThrow(new KeyringException("message")); + Keyring comparatorKeyring = ComparatorKeyring.create(actualKeyring, secondKeyring); + + assertThat(comparatorKeyring.getRdeSigningKey()).isSameAs(keyPair); + + assertAboutLogs() + .that(testLogHandler) + .hasLogAtLevelWithMessage( + Level.SEVERE, ".getRdeSigningKey: Only second implementation threw exception"); + } + + @Test + public void testRdeSigningKey_bothThrow() { + Keyring actualKeyring = mock(Keyring.class); + Keyring secondKeyring = mock(Keyring.class); + when(actualKeyring.getRdeSigningKey()).thenThrow(new KeyringException("message")); + when(secondKeyring.getRdeSigningKey()).thenThrow(new KeyringException("message")); + Keyring comparatorKeyring = ComparatorKeyring.create(actualKeyring, secondKeyring); + + try { + comparatorKeyring.getRdeSigningKey(); + fail("Should have thrown KeyringException"); + } catch (KeyringException expected) { + } + + assertAboutLogs().that(testLogHandler).hasNoLogsAtLevel(Level.SEVERE); + } + + @Test + public void testRdeSigningKey_same() throws Exception { + Keyring actualKeyring = mock(Keyring.class); + Keyring secondKeyring = mock(Keyring.class); + PGPKeyPair keyPair = + new PGPKeyPair( + mockPublicKey(false, false), + mockPrivateKey(false, false, false, false)); + PGPKeyPair keyPairCopy = + new PGPKeyPair( + mockPublicKey(false, false), + mockPrivateKey(false, false, false, false)); + when(actualKeyring.getRdeSigningKey()).thenReturn(keyPair); + when(secondKeyring.getRdeSigningKey()).thenReturn(keyPairCopy); + Keyring comparatorKeyring = ComparatorKeyring.create(actualKeyring, secondKeyring); + + assertThat(comparatorKeyring.getRdeSigningKey()).isSameAs(keyPair); + + assertAboutLogs().that(testLogHandler).hasNoLogsAtLevel(Level.SEVERE); + } + + @Test + public void testRdeSigningKey_different() throws Exception { + Keyring actualKeyring = mock(Keyring.class); + Keyring secondKeyring = mock(Keyring.class); + PGPKeyPair keyPair = + new PGPKeyPair( + mockPublicKey(false, false), + mockPrivateKey(false, false, false, false)); + PGPKeyPair keyPairDifferent = + new PGPKeyPair( + mockPublicKey(false, false), + mockPrivateKey(true, false, false, false)); + when(actualKeyring.getRdeSigningKey()).thenReturn(keyPair); + when(secondKeyring.getRdeSigningKey()).thenReturn(keyPairDifferent); + Keyring comparatorKeyring = ComparatorKeyring.create(actualKeyring, secondKeyring); + + assertThat(comparatorKeyring.getRdeSigningKey()).isSameAs(keyPair); + + String alternateKeyPairString = String.format( + "PGPKeyPair{%s, %s}", PUBLIC_KEY_TO_STRING, "PGPPrivateKey{keyId=2}"); + + assertAboutLogs() + .that(testLogHandler) + .hasLogAtLevelWithMessage( + Level.SEVERE, + String.format( + ".getRdeSigningKey: Got different results! '%s' vs '%s'", + KEY_PAIR_TO_STRING, + alternateKeyPairString)); + } +} diff --git a/javatests/google/registry/keyring/api/KeySerializerTest.java b/javatests/google/registry/keyring/api/KeySerializerTest.java new file mode 100644 index 000000000..71e47c067 --- /dev/null +++ b/javatests/google/registry/keyring/api/KeySerializerTest.java @@ -0,0 +1,167 @@ +// 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.keyring.api; + +import static com.google.common.truth.Truth.assertThat; +import static java.nio.charset.StandardCharsets.UTF_8; + +import google.registry.testing.BouncyCastleProviderRule; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPKeyPair; +import org.bouncycastle.openpgp.PGPPrivateKey; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.PGPUtil; +import org.bouncycastle.openpgp.bc.BcPGPSecretKeyRing; +import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder; +import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link KeySerializer}. */ +@RunWith(JUnit4.class) +public class KeySerializerTest { + + @Rule + public final BouncyCastleProviderRule bouncy = new BouncyCastleProviderRule(); + + /** + * An Armored representation of a pgp secret key. + * + *

This key was created specifically for tests. It has a password of "12345678". + * + *

Created using 'gpg --gen-key' followed by 'gpg --export-secret-key -a'. + */ + private static final String ARMORED_KEY_STRING = + "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + + "Version: GnuPG v1\n" + + "\n" + + "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\n" + + "GQ==\n" + + "=qeFB\n" + + "-----END PGP PRIVATE KEY BLOCK-----\n"; + + private static final BcPGPSecretKeyRing SECRET_KEYRING = getSecretKeyring(); + + private static final PGPSecretKey SECRET_KEY = SECRET_KEYRING.getSecretKey(); + + private static final PGPPublicKey PUBLIC_KEY = SECRET_KEY.getPublicKey(); + + private static final PGPPrivateKey PRIVATE_KEY = extractPrivateKey(SECRET_KEY, "12345678"); + + // Used for static initialization only. + private static BcPGPSecretKeyRing getSecretKeyring() { + try { + return new BcPGPSecretKeyRing( + PGPUtil.getDecoderStream(new ByteArrayInputStream(ARMORED_KEY_STRING.getBytes(UTF_8)))); + } catch (IOException | PGPException e) { + throw new Error(e); + } + } + + // Used for static initialization only. + private static PGPPrivateKey extractPrivateKey(PGPSecretKey secretKey, String password) { + try { + return secretKey.extractPrivateKey( + new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()) + .build(password.toCharArray())); + } catch (PGPException e) { + throw new Error(e); + } + } + + @Test public void serializeString() throws Exception { + String result = KeySerializer.deserializeString(KeySerializer.serializeString("value\n")); + assertThat(result).isEqualTo("value\n"); + } + + @Test public void serializePublicKey() throws Exception { + PGPPublicKey publicKeyResult = + KeySerializer.deserializePublicKey( + KeySerializer.serializePublicKey(PUBLIC_KEY)); + + assertThat(publicKeyResult.getEncoded()).isEqualTo(PUBLIC_KEY.getEncoded()); + } + + @Test public void serializeKeyPair() throws Exception { + PGPKeyPair keyPairResult = + KeySerializer.deserializeKeyPair( + KeySerializer.serializeKeyPair(new PGPKeyPair(PUBLIC_KEY, PRIVATE_KEY))); + + assertThat(keyPairResult.getPublicKey().getEncoded()).isEqualTo(PUBLIC_KEY.getEncoded()); + assertThat(keyPairResult.getPrivateKey().getKeyID()).isEqualTo(PRIVATE_KEY.getKeyID()); + assertThat(keyPairResult.getPrivateKey().getPrivateKeyDataPacket().getEncoded()) + .isEqualTo(PRIVATE_KEY.getPrivateKeyDataPacket().getEncoded()); + assertThat(keyPairResult.getPrivateKey().getPublicKeyPacket().getEncoded()) + .isEqualTo(PRIVATE_KEY.getPublicKeyPacket().getEncoded()); + } +}