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.util.NoSuchElementException;
008import javax.annotation.Nonnull;
009
010import org.bouncycastle.openpgp.PGPKeyRing;
011import org.bouncycastle.openpgp.PGPPublicKey;
012
013/**
014 * Tuple class used to identify a subkey by fingerprints of the primary key of the subkeys key ring,
015 * as well as the subkeys fingerprint.
016 */
017public class SubkeyIdentifier {
018
019    private final OpenPgpFingerprint primaryKeyFingerprint;
020    private final OpenPgpFingerprint subkeyFingerprint;
021
022    /**
023     * Create a {@link SubkeyIdentifier} from a {@link PGPKeyRing}.
024     * The identifier will point to the primary key of the provided ring.
025     *
026     * @param keyRing key ring
027     */
028    public SubkeyIdentifier(PGPKeyRing keyRing) {
029        this(keyRing, keyRing.getPublicKey().getKeyID());
030    }
031
032    /**
033     * Create a {@link SubkeyIdentifier} from a {@link PGPKeyRing} and the subkeys key id.
034     * {@link #getPrimaryKeyFingerprint()} will return the {@link OpenPgpFingerprint} of the keyrings primary key,
035     * while {@link #getSubkeyFingerprint()} will return the subkeys fingerprint.
036     *
037     * @param keyRing keyring the subkey belongs to
038     * @param keyId keyid of the subkey
039     */
040    public SubkeyIdentifier(@Nonnull PGPKeyRing keyRing, long keyId) {
041        PGPPublicKey subkey = keyRing.getPublicKey(keyId);
042        if (subkey == null) {
043            throw new NoSuchElementException("Key ring does not contain subkey with id " + Long.toHexString(keyId));
044        }
045        this.primaryKeyFingerprint = OpenPgpFingerprint.of(keyRing);
046        this.subkeyFingerprint = OpenPgpFingerprint.of(subkey);
047    }
048
049    public SubkeyIdentifier(@Nonnull PGPKeyRing keyRing, @Nonnull OpenPgpFingerprint subkeyFingerprint) {
050        this(OpenPgpFingerprint.of(keyRing), subkeyFingerprint);
051    }
052
053    /**
054     * Create a {@link SubkeyIdentifier} that identifies the primary key with the given fingerprint.
055     * This means, both {@link #getPrimaryKeyFingerprint()} and {@link #getSubkeyFingerprint()} return the same.
056     *
057     * @param primaryKeyFingerprint fingerprint of the identified key
058     */
059    public SubkeyIdentifier(@Nonnull OpenPgpFingerprint primaryKeyFingerprint) {
060        this(primaryKeyFingerprint, primaryKeyFingerprint);
061    }
062
063    /**
064     * Create a {@link SubkeyIdentifier} which points to the subkey with the given subkeyFingerprint,
065     * which has a primary key with the given primaryKeyFingerprint.
066     *
067     * @param primaryKeyFingerprint fingerprint of the primary key
068     * @param subkeyFingerprint fingerprint of the subkey
069     */
070    public SubkeyIdentifier(@Nonnull OpenPgpFingerprint primaryKeyFingerprint, @Nonnull OpenPgpFingerprint subkeyFingerprint) {
071        this.primaryKeyFingerprint = primaryKeyFingerprint;
072        this.subkeyFingerprint = subkeyFingerprint;
073    }
074
075    public @Nonnull OpenPgpFingerprint getFingerprint() {
076        return getSubkeyFingerprint();
077    }
078
079    public long getKeyId() {
080        return getSubkeyId();
081    }
082
083    /**
084     * Return the {@link OpenPgpFingerprint} of the primary key of the identified key.
085     * This might be the same as {@link #getSubkeyFingerprint()} if the identified subkey is the primary key.
086     *
087     * @return primary key fingerprint
088     */
089    public @Nonnull OpenPgpFingerprint getPrimaryKeyFingerprint() {
090        return primaryKeyFingerprint;
091    }
092
093    /**
094     * Return the key id of the primary key of the identified key.
095     * This might be the same as {@link #getSubkeyId()} if the identified subkey is the primary key.
096     *
097     * @return primary key id
098     */
099    public long getPrimaryKeyId() {
100        return getPrimaryKeyFingerprint().getKeyId();
101    }
102
103    /**
104     * Return the {@link OpenPgpFingerprint} of the identified subkey.
105     *
106     * @return subkey fingerprint
107     */
108    public @Nonnull OpenPgpFingerprint getSubkeyFingerprint() {
109        return subkeyFingerprint;
110    }
111
112    /**
113     * Return the key id of the identified subkey.
114     *
115     * @return subkey id
116     */
117    public long getSubkeyId() {
118        return getSubkeyFingerprint().getKeyId();
119    }
120
121    @Override
122    public int hashCode() {
123        return primaryKeyFingerprint.hashCode() * 31 + subkeyFingerprint.hashCode();
124    }
125
126    @Override
127    public boolean equals(Object obj) {
128        if (obj == null) {
129            return false;
130        }
131        if (this == obj) {
132            return true;
133        }
134        if (!(obj instanceof SubkeyIdentifier)) {
135            return false;
136        }
137        SubkeyIdentifier other = (SubkeyIdentifier) obj;
138        return getPrimaryKeyFingerprint().equals(other.getPrimaryKeyFingerprint())
139                && getSubkeyFingerprint().equals(other.getSubkeyFingerprint());
140    }
141
142    @Override
143    public String toString() {
144        return getSubkeyFingerprint() + " " + getPrimaryKeyFingerprint();
145    }
146}