mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-12-25 04:17:59 +01:00
Kolin conversion: Some OpenPgpFingerprint classes
This commit is contained in:
parent
85f28e608a
commit
a4bfed559d
6 changed files with 297 additions and 457 deletions
|
@ -1,189 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// 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.PGPSecretKey;
|
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
|
||||||
import org.bouncycastle.util.encoders.Hex;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Abstract super class of different version OpenPGP fingerprints.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public abstract class OpenPgpFingerprint implements CharSequence, Comparable<OpenPgpFingerprint> {
|
|
||||||
@SuppressWarnings("CharsetObjectCanBeUsed")
|
|
||||||
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(PGPSecretKey key) {
|
|
||||||
return of(key.getPublicKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
if (key.getVersion() == 5) {
|
|
||||||
return new OpenPgpV5Fingerprint(key);
|
|
||||||
}
|
|
||||||
if (key.getVersion() == 6) {
|
|
||||||
return new OpenPgpV6Fingerprint(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());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Try to parse an {@link OpenPgpFingerprint} from the given fingerprint string.
|
|
||||||
* If the trimmed fingerprint without whitespace is 64 characters long, it is either a v5 or v6 fingerprint.
|
|
||||||
* In this case, we return a {@link _64DigitFingerprint}. Since this is ambiguous, it is generally recommended
|
|
||||||
* to know the version of the key beforehand.
|
|
||||||
*
|
|
||||||
* @param fingerprint fingerprint
|
|
||||||
* @return parsed fingerprint
|
|
||||||
* @deprecated Use the constructor methods of the versioned fingerprint subclasses instead.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public static OpenPgpFingerprint parse(String fingerprint) {
|
|
||||||
String fp = fingerprint.replace(" ", "").trim().toUpperCase();
|
|
||||||
if (fp.matches("^[0-9A-F]{40}$")) {
|
|
||||||
return new OpenPgpV4Fingerprint(fp);
|
|
||||||
}
|
|
||||||
if (fp.matches("^[0-9A-F]{64}$")) {
|
|
||||||
// Might be v5 or v6 :/
|
|
||||||
return new _64DigitFingerprint(fp);
|
|
||||||
}
|
|
||||||
throw new IllegalArgumentException("Fingerprint does not appear to match any known fingerprint patterns.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse a binary OpenPGP fingerprint into an {@link OpenPgpFingerprint} object.
|
|
||||||
*
|
|
||||||
* @param binaryFingerprint binary representation of the fingerprint
|
|
||||||
* @return parsed fingerprint
|
|
||||||
* @deprecated use the parse() methods of the versioned fingerprint subclasses instead.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public static OpenPgpFingerprint parseFromBinary(byte[] binaryFingerprint) {
|
|
||||||
String hex = Hex.toHexString(binaryFingerprint).toUpperCase();
|
|
||||||
return parse(hex);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 <a href="https://tools.ietf.org/html/rfc4880#section-12.2">
|
|
||||||
* RFC-4880 §12.2: Key IDs and Fingerprints</a>
|
|
||||||
* @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();
|
|
||||||
}
|
|
|
@ -0,0 +1,158 @@
|
||||||
|
package org.pgpainless.key
|
||||||
|
|
||||||
|
import org.bouncycastle.openpgp.PGPKeyRing
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKey
|
||||||
|
import org.bouncycastle.openpgp.PGPSecretKey
|
||||||
|
import org.bouncycastle.util.encoders.Hex
|
||||||
|
import java.nio.charset.Charset
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract super class of different version OpenPGP fingerprints.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
abstract class OpenPgpFingerprint : CharSequence, Comparable<OpenPgpFingerprint> {
|
||||||
|
val fingerprint: String
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the version of the fingerprint.
|
||||||
|
*
|
||||||
|
* @return version
|
||||||
|
*/
|
||||||
|
abstract fun getVersion(): Int
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 <a href="https://tools.ietf.org/html/rfc4880#section-12.2">
|
||||||
|
* RFC-4880 §12.2: Key IDs and Fingerprints</a>
|
||||||
|
* @return key id
|
||||||
|
*/
|
||||||
|
abstract val keyId: Long
|
||||||
|
|
||||||
|
constructor(fingerprint: String) {
|
||||||
|
val prep = fingerprint.replace(" ", "").trim().uppercase()
|
||||||
|
if (!isValid(prep)) {
|
||||||
|
throw IllegalArgumentException("Fingerprint '$fingerprint' does not appear to be a valid OpenPGP V${getVersion()} fingerprint.")
|
||||||
|
}
|
||||||
|
this.fingerprint = prep
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(bytes: ByteArray): this(Hex.toHexString(bytes))
|
||||||
|
|
||||||
|
constructor(key: PGPPublicKey): this(key.fingerprint) {
|
||||||
|
if (key.version != getVersion()) {
|
||||||
|
throw IllegalArgumentException("Key is not a v${getVersion()} OpenPgp key.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(key: PGPSecretKey): this(key.publicKey)
|
||||||
|
|
||||||
|
constructor(keys: PGPKeyRing): this(keys.publicKey)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check, whether the fingerprint consists of 40 valid hexadecimal characters.
|
||||||
|
* @param fp fingerprint to check.
|
||||||
|
* @return true if fingerprint is valid.
|
||||||
|
*/
|
||||||
|
protected abstract fun isValid(fingerprint: String): Boolean
|
||||||
|
|
||||||
|
override val length: Int
|
||||||
|
get() = fingerprint.length
|
||||||
|
|
||||||
|
override fun get(index: Int) = fingerprint.get(index)
|
||||||
|
|
||||||
|
override fun subSequence(startIndex: Int, endIndex: Int) = fingerprint.subSequence(startIndex, endIndex)
|
||||||
|
override fun compareTo(other: OpenPgpFingerprint): Int {
|
||||||
|
return fingerprint.compareTo(other.fingerprint)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
return toString() == other.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return toString().hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String = fingerprint
|
||||||
|
|
||||||
|
abstract fun prettyPrint(): String
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
val utf8: Charset = Charset.forName("UTF-8")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the fingerprint of the given key.
|
||||||
|
* This method automatically matches key versions to fingerprint implementations.
|
||||||
|
*
|
||||||
|
* @param key key
|
||||||
|
* @return fingerprint
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
fun of(key: PGPSecretKey): OpenPgpFingerprint = of(key.publicKey)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the fingerprint of the given key.
|
||||||
|
* This method automatically matches key versions to fingerprint implementations.
|
||||||
|
*
|
||||||
|
* @param key key
|
||||||
|
* @return fingerprint
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
fun of (key: PGPPublicKey): OpenPgpFingerprint = when(key.version) {
|
||||||
|
4 -> OpenPgpV4Fingerprint(key)
|
||||||
|
5 -> OpenPgpV5Fingerprint(key)
|
||||||
|
6 -> OpenPgpV6Fingerprint(key)
|
||||||
|
else -> throw IllegalArgumentException("OpenPGP keys of version ${key.version} 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
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
fun of (keys: PGPKeyRing): OpenPgpFingerprint = of(keys.publicKey)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to parse an {@link OpenPgpFingerprint} from the given fingerprint string.
|
||||||
|
* If the trimmed fingerprint without whitespace is 64 characters long, it is either a v5 or v6 fingerprint.
|
||||||
|
* In this case, we return a {@link _64DigitFingerprint}. Since this is ambiguous, it is generally recommended
|
||||||
|
* to know the version of the key beforehand.
|
||||||
|
*
|
||||||
|
* @param fingerprint fingerprint
|
||||||
|
* @return parsed fingerprint
|
||||||
|
* @deprecated Use the constructor methods of the versioned fingerprint subclasses instead.
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
@Deprecated("Use the constructor methods of the versioned fingerprint subclasses instead.")
|
||||||
|
fun parse(fingerprint: String): OpenPgpFingerprint {
|
||||||
|
val prep = fingerprint.replace(" ", "").trim().uppercase()
|
||||||
|
if (prep.matches("^[0-9A-F]{40}$".toRegex())) {
|
||||||
|
return OpenPgpV4Fingerprint(prep)
|
||||||
|
}
|
||||||
|
if (prep.matches("^[0-9A-F]{64}$".toRegex())) {
|
||||||
|
// Might be v5 or v6 :/
|
||||||
|
return _64DigitFingerprint(prep)
|
||||||
|
}
|
||||||
|
throw IllegalArgumentException("Fingerprint does not appear to match any known fingerprint pattern.")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a binary OpenPGP fingerprint into an {@link OpenPgpFingerprint} object.
|
||||||
|
*
|
||||||
|
* @param binaryFingerprint binary representation of the fingerprint
|
||||||
|
* @return parsed fingerprint
|
||||||
|
* @deprecated use the parse() methods of the versioned fingerprint subclasses instead.
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
@Deprecated("use the parse() methods of the versioned fingerprint subclasses instead.")
|
||||||
|
fun parseFromBinary(binaryFingerprint: ByteArray): OpenPgpFingerprint =
|
||||||
|
parse(Hex.toHexString(binaryFingerprint))
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,149 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2018 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package org.pgpainless.key;
|
|
||||||
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.nio.Buffer;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
|
|
||||||
import org.bouncycastle.openpgp.PGPKeyRing;
|
|
||||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
|
||||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
|
||||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
|
||||||
import org.bouncycastle.util.encoders.Hex;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class represents a hex encoded, uppercase OpenPGP v4 fingerprint.
|
|
||||||
*/
|
|
||||||
public class OpenPgpV4Fingerprint extends OpenPgpFingerprint {
|
|
||||||
|
|
||||||
public static final String SCHEME = "openpgp4fpr";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create an {@link OpenPgpV4Fingerprint}.
|
|
||||||
*
|
|
||||||
* @param fingerprint uppercase hexadecimal fingerprint of length 40
|
|
||||||
*/
|
|
||||||
public OpenPgpV4Fingerprint(@Nonnull String fingerprint) {
|
|
||||||
super(fingerprint);
|
|
||||||
}
|
|
||||||
|
|
||||||
public OpenPgpV4Fingerprint(@Nonnull byte[] bytes) {
|
|
||||||
super(Hex.encode(bytes));
|
|
||||||
}
|
|
||||||
|
|
||||||
public OpenPgpV4Fingerprint(@Nonnull PGPPublicKey key) {
|
|
||||||
super(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
public OpenPgpV4Fingerprint(@Nonnull PGPSecretKey key) {
|
|
||||||
this(key.getPublicKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
public OpenPgpV4Fingerprint(@Nonnull PGPPublicKeyRing ring) {
|
|
||||||
super(ring);
|
|
||||||
}
|
|
||||||
|
|
||||||
public OpenPgpV4Fingerprint(@Nonnull PGPSecretKeyRing ring) {
|
|
||||||
super(ring);
|
|
||||||
}
|
|
||||||
|
|
||||||
public OpenPgpV4Fingerprint(@Nonnull PGPKeyRing ring) {
|
|
||||||
super(ring);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getVersion() {
|
|
||||||
return 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean isValid(@Nonnull String fp) {
|
|
||||||
return fp.matches("^[0-9A-F]{40}$");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getKeyId() {
|
|
||||||
byte[] bytes = Hex.decode(toString().getBytes(utf8));
|
|
||||||
ByteBuffer buf = ByteBuffer.wrap(bytes);
|
|
||||||
|
|
||||||
// The key id is the right-most 8 bytes (conveniently a long)
|
|
||||||
// We have to cast here in order to be compatible with java 8
|
|
||||||
// https://github.com/eclipse/jetty.project/issues/3244
|
|
||||||
((Buffer) buf).position(12); // 20 - 8 bytes = offset 12
|
|
||||||
|
|
||||||
return buf.getLong();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String prettyPrint() {
|
|
||||||
String fp = toString();
|
|
||||||
StringBuilder pretty = new StringBuilder();
|
|
||||||
for (int i = 0; i < 5; i++) {
|
|
||||||
pretty.append(fp, i * 4, (i + 1) * 4).append(' ');
|
|
||||||
}
|
|
||||||
pretty.append(' ');
|
|
||||||
for (int i = 5; i < 9; i++) {
|
|
||||||
pretty.append(fp, i * 4, (i + 1) * 4).append(' ');
|
|
||||||
}
|
|
||||||
pretty.append(fp, 36, 40);
|
|
||||||
return pretty.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object other) {
|
|
||||||
if (other == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(other instanceof CharSequence)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.toString().equals(other.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return toString().hashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the fingerprint as an openpgp4fpr {@link URI}.
|
|
||||||
* An example would be 'openpgp4fpr:7F9116FEA90A5983936C7CFAA027DB2F3E1E118A'.
|
|
||||||
*
|
|
||||||
* @return openpgp4fpr fingerprint uri
|
|
||||||
* @see <a href="https://metacode.biz/openpgp/openpgp4fpr">openpgp4fpr URI scheme</a>
|
|
||||||
*/
|
|
||||||
public URI toUri() {
|
|
||||||
try {
|
|
||||||
return new URI(OpenPgpV4Fingerprint.SCHEME, toString(), null);
|
|
||||||
} catch (URISyntaxException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert an openpgp4fpr URI to an {@link OpenPgpV4Fingerprint}.
|
|
||||||
*
|
|
||||||
* @param uri {@link URI} with scheme 'openpgp4fpr'
|
|
||||||
* @return fingerprint parsed from the uri
|
|
||||||
* @see <a href="https://metacode.biz/openpgp/openpgp4fpr">openpgp4fpr URI scheme</a>
|
|
||||||
*/
|
|
||||||
public static OpenPgpV4Fingerprint fromUri(URI uri) {
|
|
||||||
if (!SCHEME.equals(uri.getScheme())) {
|
|
||||||
throw new IllegalArgumentException("URI scheme MUST equal '" + SCHEME + "'");
|
|
||||||
}
|
|
||||||
return new OpenPgpV4Fingerprint(uri.getSchemeSpecificPart());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compareTo(@Nonnull OpenPgpFingerprint openPgpFingerprint) {
|
|
||||||
return toString().compareTo(openPgpFingerprint.toString());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
package org.pgpainless.key
|
||||||
|
|
||||||
|
import org.bouncycastle.openpgp.PGPKeyRing
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKey
|
||||||
|
import org.bouncycastle.openpgp.PGPSecretKey
|
||||||
|
import org.bouncycastle.util.encoders.Hex
|
||||||
|
import java.net.URI
|
||||||
|
import java.nio.Buffer
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.nio.charset.Charset
|
||||||
|
|
||||||
|
class OpenPgpV4Fingerprint: OpenPgpFingerprint {
|
||||||
|
|
||||||
|
constructor(fingerprint: String): super(fingerprint)
|
||||||
|
constructor(bytes: ByteArray): super(bytes)
|
||||||
|
constructor(key: PGPPublicKey): super(key)
|
||||||
|
constructor(key: PGPSecretKey): super(key)
|
||||||
|
constructor(keys: PGPKeyRing): super(keys)
|
||||||
|
|
||||||
|
override fun getVersion() = 4
|
||||||
|
|
||||||
|
override val keyId: Long
|
||||||
|
get() {
|
||||||
|
val bytes = Hex.decode(toString().toByteArray(Charset.forName("UTF-8")))
|
||||||
|
val buf = ByteBuffer.wrap(bytes)
|
||||||
|
|
||||||
|
// The key id is the right-most 8 bytes (conveniently a long)
|
||||||
|
// We have to cast here in order to be compatible with java 8
|
||||||
|
// https://github.com/eclipse/jetty.project/issues/3244
|
||||||
|
(buf as Buffer).position(12) // 20 - 8 bytes = offset 12
|
||||||
|
return buf.getLong()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isValid(fingerprint: String): Boolean {
|
||||||
|
return fingerprint.matches("^[0-9A-F]{40}$".toRegex())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toUri(): URI = URI(SCHEME, toString(), null)
|
||||||
|
|
||||||
|
override fun prettyPrint(): String {
|
||||||
|
return buildString {
|
||||||
|
for (i in 0..4) {
|
||||||
|
append(fingerprint, i * 4, (i + 1) * 4).append(' ')
|
||||||
|
}
|
||||||
|
append(' ')
|
||||||
|
for (i in 5 .. 8) {
|
||||||
|
append(fingerprint, i * 4, (i + 1) * 4).append(' ')
|
||||||
|
}
|
||||||
|
append(fingerprint, 36, 40)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
val SCHEME = "openpgp4fpr"
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun fromUri(uri: URI): OpenPgpV4Fingerprint {
|
||||||
|
if (SCHEME != uri.scheme) {
|
||||||
|
throw IllegalArgumentException("URI scheme MUST equal '$SCHEME'.")
|
||||||
|
}
|
||||||
|
return OpenPgpV4Fingerprint(uri.schemeSpecificPart)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,119 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package org.pgpainless.key;
|
|
||||||
|
|
||||||
import java.nio.Buffer;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
|
|
||||||
import org.bouncycastle.openpgp.PGPKeyRing;
|
|
||||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
|
||||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
|
||||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
|
||||||
import org.bouncycastle.util.encoders.Hex;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class represents a hex encoded, upper case OpenPGP v5 or v6 fingerprint.
|
|
||||||
* Since both fingerprints use the same format, this class is used when parsing the fingerprint without knowing the
|
|
||||||
* key version.
|
|
||||||
*/
|
|
||||||
public class _64DigitFingerprint extends OpenPgpFingerprint {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create an {@link _64DigitFingerprint}.
|
|
||||||
*
|
|
||||||
* @param fingerprint uppercase hexadecimal fingerprint of length 64
|
|
||||||
*/
|
|
||||||
protected _64DigitFingerprint(@Nonnull String fingerprint) {
|
|
||||||
super(fingerprint);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected _64DigitFingerprint(@Nonnull byte[] bytes) {
|
|
||||||
super(Hex.encode(bytes));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected _64DigitFingerprint(@Nonnull PGPPublicKey key) {
|
|
||||||
super(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected _64DigitFingerprint(@Nonnull PGPSecretKey key) {
|
|
||||||
this(key.getPublicKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected _64DigitFingerprint(@Nonnull PGPPublicKeyRing ring) {
|
|
||||||
super(ring);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected _64DigitFingerprint(@Nonnull PGPSecretKeyRing ring) {
|
|
||||||
super(ring);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected _64DigitFingerprint(@Nonnull PGPKeyRing ring) {
|
|
||||||
super(ring);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getVersion() {
|
|
||||||
return -1; // might be v5 or v6
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean isValid(@Nonnull String fp) {
|
|
||||||
return fp.matches("^[0-9A-F]{64}$");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getKeyId() {
|
|
||||||
byte[] bytes = Hex.decode(toString().getBytes(utf8));
|
|
||||||
ByteBuffer buf = ByteBuffer.wrap(bytes);
|
|
||||||
|
|
||||||
// The key id is the left-most 8 bytes (conveniently a long).
|
|
||||||
// We have to cast here in order to be compatible with java 8
|
|
||||||
// https://github.com/eclipse/jetty.project/issues/3244
|
|
||||||
((Buffer) buf).position(0);
|
|
||||||
|
|
||||||
return buf.getLong();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String prettyPrint() {
|
|
||||||
String fp = toString();
|
|
||||||
StringBuilder pretty = new StringBuilder();
|
|
||||||
|
|
||||||
for (int i = 0; i < 4; i++) {
|
|
||||||
pretty.append(fp, i * 8, (i + 1) * 8).append(' ');
|
|
||||||
}
|
|
||||||
pretty.append(' ');
|
|
||||||
for (int i = 4; i < 7; i++) {
|
|
||||||
pretty.append(fp, i * 8, (i + 1) * 8).append(' ');
|
|
||||||
}
|
|
||||||
pretty.append(fp, 56, 64);
|
|
||||||
return pretty.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object other) {
|
|
||||||
if (other == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(other instanceof CharSequence)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.toString().equals(other.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return toString().hashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compareTo(OpenPgpFingerprint openPgpFingerprint) {
|
|
||||||
return toString().compareTo(openPgpFingerprint.toString());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package org.pgpainless.key;
|
||||||
|
|
||||||
|
import java.nio.Buffer;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
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.PGPSecretKey;
|
||||||
|
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||||
|
import org.bouncycastle.util.encoders.Hex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class represents a hex encoded, upper case OpenPGP v5 or v6 fingerprint.
|
||||||
|
* Since both fingerprints use the same format, this class is used when parsing the fingerprint without knowing the
|
||||||
|
* key version.
|
||||||
|
*/
|
||||||
|
open class _64DigitFingerprint: OpenPgpFingerprint {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an {@link _64DigitFingerprint}.
|
||||||
|
*
|
||||||
|
* @param fingerprint uppercase hexadecimal fingerprint of length 64
|
||||||
|
*/
|
||||||
|
constructor(fingerprint: String): super(fingerprint)
|
||||||
|
constructor(bytes: ByteArray): super(bytes)
|
||||||
|
constructor(key: PGPPublicKey): super(key)
|
||||||
|
constructor(key: PGPSecretKey): super(key)
|
||||||
|
constructor(keys: PGPKeyRing): super(keys)
|
||||||
|
|
||||||
|
override val keyId: Long
|
||||||
|
get() {
|
||||||
|
val bytes = Hex.decode(fingerprint.toByteArray(Charset.forName("UTF-8")))
|
||||||
|
val buf = ByteBuffer.wrap(bytes);
|
||||||
|
|
||||||
|
// The key id is the left-most 8 bytes (conveniently a long).
|
||||||
|
// We have to cast here in order to be compatible with java 8
|
||||||
|
// https://github.com/eclipse/jetty.project/issues/3244
|
||||||
|
(buf as Buffer).position(0);
|
||||||
|
|
||||||
|
return buf.getLong()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getVersion(): Int {
|
||||||
|
return -1 // might be v5 or v6
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isValid(fingerprint: String): Boolean {
|
||||||
|
return fingerprint.matches(("^[0-9A-F]{64}$".toRegex()))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return super.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun prettyPrint(): String {
|
||||||
|
return buildString {
|
||||||
|
for (i in 0 until 4) {
|
||||||
|
append(fingerprint, i * 8, (i + 1) * 8).append(' ');
|
||||||
|
}
|
||||||
|
append(' ');
|
||||||
|
for (i in 4 until 7) {
|
||||||
|
append(fingerprint, i * 8, (i + 1) * 8).append(' ');
|
||||||
|
}
|
||||||
|
append(fingerprint, 56, 64);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue