From 383f51277eea2390034bde1c120c6638748b26b6 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 27 Oct 2021 17:12:06 +0200 Subject: [PATCH] Prepare for V5 keys: Extract abstract super class OpenPgpFingerprint from OpenPgpV4Fingerprint --- .../pgpainless/key/OpenPgpFingerprint.java | 135 +++++++++++++++++ .../pgpainless/key/OpenPgpV4Fingerprint.java | 137 +++++++----------- 2 files changed, 185 insertions(+), 87 deletions(-) create mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpFingerprint.java diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpFingerprint.java b/pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpFingerprint.java new file mode 100644 index 00000000..e9db3f5e --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpFingerprint.java @@ -0,0 +1,135 @@ +// SPDX-FileCopyrightText: 2021 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key; + +import java.nio.charset.Charset; +import javax.annotation.Nonnull; + +import org.bouncycastle.openpgp.PGPKeyRing; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.util.encoders.Hex; + +/** + * Abstract super class of different version OpenPGP fingerprints. + * + * @param subclass type + */ +public abstract class OpenPgpFingerprint implements CharSequence, Comparable { + protected static final Charset utf8 = Charset.forName("UTF-8"); + protected final String fingerprint; + + /** + * Return the fingerprint of the given key. + * This method automatically matches key versions to fingerprint implementations. + * + * @param key key + * @return fingerprint + */ + public static OpenPgpFingerprint of(PGPPublicKey key) { + if (key.getVersion() == 4) { + return new OpenPgpV4Fingerprint(key); + } + throw new IllegalArgumentException("OpenPGP keys of version " + key.getVersion() + " are not supported."); + } + + /** + * Return the fingerprint of the primary key of the given key ring. + * This method automatically matches key versions to fingerprint implementations. + * + * @param ring key ring + * @return fingerprint + */ + public static OpenPgpFingerprint of(PGPKeyRing ring) { + return of(ring.getPublicKey()); + } + + public OpenPgpFingerprint(String fingerprint) { + String fp = fingerprint.replace(" ", "").trim().toUpperCase(); + if (!isValid(fp)) { + throw new IllegalArgumentException( + String.format("Fingerprint '%s' does not appear to be a valid OpenPGP V%d fingerprint.", fingerprint, getVersion()) + ); + } + this.fingerprint = fp; + } + + public OpenPgpFingerprint(@Nonnull byte[] bytes) { + this(new String(bytes, utf8)); + } + + public OpenPgpFingerprint(PGPPublicKey key) { + this(Hex.encode(key.getFingerprint())); + if (key.getVersion() != getVersion()) { + throw new IllegalArgumentException(String.format("Key is not a v%d OpenPgp key.", getVersion())); + } + } + + public OpenPgpFingerprint(@Nonnull PGPPublicKeyRing ring) { + this(ring.getPublicKey()); + } + + public OpenPgpFingerprint(@Nonnull PGPSecretKeyRing ring) { + this(ring.getPublicKey()); + } + + public OpenPgpFingerprint(@Nonnull PGPKeyRing ring) { + this(ring.getPublicKey()); + } + + /** + * Return the version of the fingerprint. + * + * @return version + */ + public abstract int getVersion(); + + /** + * Check, whether the fingerprint consists of 40 valid hexadecimal characters. + * @param fp fingerprint to check. + * @return true if fingerprint is valid. + */ + protected abstract boolean isValid(@Nonnull String fp); + + /** + * Return the key id of the OpenPGP public key this {@link OpenPgpFingerprint} belongs to. + * This method can be implemented for V4 and V5 fingerprints. + * V3 key-IDs cannot be derived from the fingerprint, but we don't care, since V3 is deprecated. + * + * @see + * RFC-4880 §12.2: Key IDs and Fingerprints + * @return key id + */ + public abstract long getKeyId(); + + @Override + public int length() { + return fingerprint.length(); + } + + @Override + public char charAt(int i) { + return fingerprint.charAt(i); + } + + @Override + public CharSequence subSequence(int i, int i1) { + return fingerprint.subSequence(i, i1); + } + + @Override + @Nonnull + public String toString() { + return fingerprint; + } + + /** + * Return a pretty printed representation of the fingerprint. + * + * @return pretty printed fingerprint + */ + public abstract String prettyPrint(); +} diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpV4Fingerprint.java b/pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpV4Fingerprint.java index b368e370..a1e6eb7f 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpV4Fingerprint.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpV4Fingerprint.java @@ -8,7 +8,6 @@ import java.net.URI; import java.net.URISyntaxException; import java.nio.Buffer; import java.nio.ByteBuffer; -import java.nio.charset.Charset; import javax.annotation.Nonnull; import org.bouncycastle.openpgp.PGPKeyRing; @@ -21,13 +20,10 @@ import org.bouncycastle.util.encoders.Hex; /** * This class represents an hex encoded, uppercase OpenPGP v4 fingerprint. */ -public class OpenPgpV4Fingerprint implements CharSequence, Comparable { +public class OpenPgpV4Fingerprint extends OpenPgpFingerprint { public static final String SCHEME = "openpgp4fpr"; - private static final Charset utf8 = Charset.forName("UTF-8"); - private final String fingerprint; - /** * Create an {@link OpenPgpV4Fingerprint}. * @see @@ -35,57 +31,20 @@ public class OpenPgpV4Fingerprint implements CharSequence, Comparable - * RFC-4880 §12.2: Key IDs and Fingerprints - * @return key id - */ + @Override public long getKeyId() { byte[] bytes = Hex.decode(toString().getBytes(utf8)); ByteBuffer buf = ByteBuffer.wrap(bytes); @@ -97,6 +56,45 @@ public class OpenPgpV4Fingerprint implements CharSequence, Comparable