diff --git a/java/google/registry/keyring/api/BUILD b/java/google/registry/keyring/api/BUILD index 56fa9c4d9..315e945c7 100644 --- a/java/google/registry/keyring/api/BUILD +++ b/java/google/registry/keyring/api/BUILD @@ -9,6 +9,7 @@ java_library( srcs = glob(["*.java"]), resources = glob(["*.asc"]), deps = [ + "//java/google/registry/util", "@com_google_code_findbugs_jsr305", "@com_google_dagger", "@com_google_guava", diff --git a/java/google/registry/keyring/api/ComparatorKeyring.java b/java/google/registry/keyring/api/ComparatorKeyring.java new file mode 100644 index 000000000..8b28ad55a --- /dev/null +++ b/java/google/registry/keyring/api/ComparatorKeyring.java @@ -0,0 +1,198 @@ +// 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 com.google.common.annotations.VisibleForTesting; +import com.google.common.base.MoreObjects; + +import google.registry.util.ComparingInvocationHandler; +import google.registry.util.FormattingLogger; + +import java.io.IOException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Objects; + +import javax.annotation.Nullable; + +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; + +/** + * Checks that a second keyring returns the same result as the current one. + * + *
Will behave exactly like the "actualKeyring" - as in will throw / return the exact same values + * - no matter what the "secondKeyring" does. But will log a warning if "secondKeyring" acts + * differently than "actualKeyring". + * + *
If both keyrings threw exceptions, there is no check whether the exeptions are the same. The
+ * assumption is that an error happened in both, but they might report that error differently.
+ */
+final class ComparatorKeyring extends ComparingInvocationHandler This proxy will log any differences in return value or thrown exceptions with "second".
+ */
+ public static Keyring create(Keyring original, Keyring second) {
+ return new ComparatorKeyring(original, second).makeProxy();
+ }
+
+ @Override
+ protected void log(Method method, String message) {
+ logger.severefmt("ComparatorKeyring.%s: %s", method.getName(), message);
+ }
+
+ /** Implements equals for the PGP classes. */
+ @Override
+ protected boolean compareResults(Method method, @Nullable Object a, @Nullable Object b) {
+ Class> clazz = method.getReturnType();
+ if (PGPPublicKey.class.equals(clazz)) {
+ return compare((PGPPublicKey) a, (PGPPublicKey) b);
+ }
+ if (PGPPrivateKey.class.equals(clazz)) {
+ return compare((PGPPrivateKey) a, (PGPPrivateKey) b);
+ }
+ if (PGPKeyPair.class.equals(clazz)) {
+ return compare((PGPKeyPair) a, (PGPKeyPair) b);
+ }
+ return super.compareResults(method, a, b);
+ }
+
+ /** Implements toString for the PGP classes. */
+ @Override
+ protected String stringifyResult(Method method, @Nullable Object a) {
+ Class> clazz = method.getReturnType();
+ if (PGPPublicKey.class.equals(clazz)) {
+ return stringify((PGPPublicKey) a);
+ }
+ if (PGPPrivateKey.class.equals(clazz)) {
+ return stringify((PGPPrivateKey) a);
+ }
+ if (PGPKeyPair.class.equals(clazz)) {
+ return stringify((PGPKeyPair) a);
+ }
+ return super.stringifyResult(method, a);
+ }
+
+ // .equals implementation for PGP types.
+
+ @VisibleForTesting
+ static boolean compare(@Nullable PGPKeyPair a, @Nullable PGPKeyPair b) {
+ if (a == null || b == null) {
+ return a == null && b == null;
+ }
+ return compare(a.getPublicKey(), b.getPublicKey())
+ && compare(a.getPrivateKey(), b.getPrivateKey());
+ }
+
+ @VisibleForTesting
+ static boolean compare(@Nullable PGPPublicKey a, @Nullable PGPPublicKey b) {
+ if (a == null || b == null) {
+ return a == null && b == null;
+ }
+ try {
+ return Arrays.equals(a.getFingerprint(), b.getFingerprint())
+ && Arrays.equals(a.getEncoded(), b.getEncoded());
+ } catch (IOException e) {
+ logger.severefmt("ComparatorKeyring error: PGPPublicKey.getEncoded failed: %s", e);
+ return false;
+ }
+ }
+
+ @VisibleForTesting
+ static boolean compare(@Nullable PGPPrivateKey a, @Nullable PGPPrivateKey b) {
+ if (a == null || b == null) {
+ return a == null && b == null;
+ }
+ return a.getKeyID() == b.getKeyID()
+ && compare(a.getPrivateKeyDataPacket(), b.getPrivateKeyDataPacket())
+ && compare(a.getPublicKeyPacket(), b.getPublicKeyPacket());
+ }
+
+ @VisibleForTesting
+ static boolean compare(PublicKeyPacket a, PublicKeyPacket b) {
+ if (a == null || b == null) {
+ return a == null && b == null;
+ }
+ try {
+ return Arrays.equals(a.getEncoded(), b.getEncoded());
+ } catch (IOException e) {
+ logger.severefmt("ComparatorKeyring error: PublicKeyPacket.getEncoded failed: %s", e);
+ return false;
+ }
+ }
+
+ @VisibleForTesting
+ static boolean compare(BCPGKey a, BCPGKey b) {
+ if (a == null || b == null) {
+ return a == null && b == null;
+ }
+ return Objects.equals(a.getFormat(), b.getFormat())
+ && Arrays.equals(a.getEncoded(), b.getEncoded());
+ }
+
+ // toString implementations
+
+ @VisibleForTesting
+ static String stringify(PGPKeyPair a) {
+ if (a == null) {
+ return "null";
+ }
+ return MoreObjects.toStringHelper(PGPKeyPair.class)
+ .addValue(stringify(a.getPublicKey()))
+ .addValue(stringify(a.getPrivateKey()))
+ .toString();
+ }
+
+ @VisibleForTesting
+ static String stringify(PGPPublicKey a) {
+ if (a == null) {
+ return "null";
+ }
+
+ StringBuilder builder = new StringBuilder("");
+ for (byte b : a.getFingerprint()) {
+ builder.append(String.format("%02x:", b));
+ }
+ return MoreObjects.toStringHelper(PGPPublicKey.class)
+ .add("fingerprint", builder.toString())
+ .toString();
+ }
+
+ @VisibleForTesting
+ static String stringify(PGPPrivateKey a) {
+ if (a == null) {
+ return "null";
+ }
+
+ // We need to be careful what information we output here. The private key should be private, and
+ // I'm not sure what is safe to put in the logs.
+ return MoreObjects.toStringHelper(PGPPrivateKey.class)
+ .add("keyId", a.getKeyID())
+ .toString();
+ }
+}