001// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
002//
003// SPDX-License-Identifier: Apache-2.0
004
005package org.pgpainless.key;
006
007import java.nio.charset.Charset;
008import javax.annotation.Nonnull;
009
010import org.bouncycastle.openpgp.PGPKeyRing;
011import org.bouncycastle.openpgp.PGPPublicKey;
012import org.bouncycastle.openpgp.PGPPublicKeyRing;
013import org.bouncycastle.openpgp.PGPSecretKeyRing;
014import org.bouncycastle.util.encoders.Hex;
015
016/**
017 * Abstract super class of different version OpenPGP fingerprints.
018 *
019 */
020public abstract class OpenPgpFingerprint implements CharSequence, Comparable<OpenPgpFingerprint> {
021    @SuppressWarnings("CharsetObjectCanBeUsed")
022    protected static final Charset utf8 = Charset.forName("UTF-8");
023    protected final String fingerprint;
024
025    /**
026     * Return the fingerprint of the given key.
027     * This method automatically matches key versions to fingerprint implementations.
028     *
029     * @param key key
030     * @return fingerprint
031     */
032    public static OpenPgpFingerprint of(PGPPublicKey key) {
033        if (key.getVersion() == 4) {
034            return new OpenPgpV4Fingerprint(key);
035        }
036        throw new IllegalArgumentException("OpenPGP keys of version " + key.getVersion() + " are not supported.");
037    }
038
039    /**
040     * Return the fingerprint of the primary key of the given key ring.
041     * This method automatically matches key versions to fingerprint implementations.
042     *
043     * @param ring key ring
044     * @return fingerprint
045     */
046    public static OpenPgpFingerprint of(PGPKeyRing ring) {
047        return of(ring.getPublicKey());
048    }
049
050    public OpenPgpFingerprint(String fingerprint) {
051        String fp = fingerprint.replace(" ", "").trim().toUpperCase();
052        if (!isValid(fp)) {
053            throw new IllegalArgumentException(
054                    String.format("Fingerprint '%s' does not appear to be a valid OpenPGP V%d fingerprint.", fingerprint, getVersion())
055            );
056        }
057        this.fingerprint = fp;
058    }
059
060    public OpenPgpFingerprint(@Nonnull byte[] bytes) {
061        this(new String(bytes, utf8));
062    }
063
064    public OpenPgpFingerprint(PGPPublicKey key) {
065        this(Hex.encode(key.getFingerprint()));
066        if (key.getVersion() != getVersion()) {
067            throw new IllegalArgumentException(String.format("Key is not a v%d OpenPgp key.", getVersion()));
068        }
069    }
070
071    public OpenPgpFingerprint(@Nonnull PGPPublicKeyRing ring) {
072        this(ring.getPublicKey());
073    }
074
075    public OpenPgpFingerprint(@Nonnull PGPSecretKeyRing ring) {
076        this(ring.getPublicKey());
077    }
078
079    public OpenPgpFingerprint(@Nonnull PGPKeyRing ring) {
080        this(ring.getPublicKey());
081    }
082
083    /**
084     * Return the version of the fingerprint.
085     *
086     * @return version
087     */
088    public abstract int getVersion();
089
090    /**
091     * Check, whether the fingerprint consists of 40 valid hexadecimal characters.
092     * @param fp fingerprint to check.
093     * @return true if fingerprint is valid.
094     */
095    protected abstract boolean isValid(@Nonnull String fp);
096
097    /**
098     * Return the key id of the OpenPGP public key this {@link OpenPgpFingerprint} belongs to.
099     * This method can be implemented for V4 and V5 fingerprints.
100     * V3 key-IDs cannot be derived from the fingerprint, but we don't care, since V3 is deprecated.
101     *
102     * @see <a href="https://tools.ietf.org/html/rfc4880#section-12.2">
103     *     RFC-4880 ยง12.2: Key IDs and Fingerprints</a>
104     * @return key id
105     */
106    public abstract long getKeyId();
107
108    @Override
109    public int length() {
110        return fingerprint.length();
111    }
112
113    @Override
114    public char charAt(int i) {
115        return fingerprint.charAt(i);
116    }
117
118    @Override
119    public CharSequence subSequence(int i, int i1) {
120        return fingerprint.subSequence(i, i1);
121    }
122
123    @Override
124    @Nonnull
125    public String toString() {
126        return fingerprint;
127    }
128
129    /**
130     * Return a pretty printed representation of the fingerprint.
131     *
132     * @return pretty printed fingerprint
133     */
134    public abstract String prettyPrint();
135}