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());
+ }
+}