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}