From cc63095ab0197539fc2c654366ee7b5388f3dcbe Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 28 Aug 2023 18:24:16 +0200 Subject: [PATCH] Kotlin conversion: SignatureSubpacketsUtil --- .../subpackets/SignatureSubpacketsUtil.java | 703 ------------------ .../subpackets/SignatureSubpacketsUtil.kt | 575 ++++++++++++++ 2 files changed, 575 insertions(+), 703 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/SignatureSubpacketsUtil.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpacketsUtil.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/SignatureSubpacketsUtil.java b/pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/SignatureSubpacketsUtil.java deleted file mode 100644 index 6d53ad1d..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/SignatureSubpacketsUtil.java +++ /dev/null @@ -1,703 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.signature.subpackets; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Date; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -import org.bouncycastle.bcpg.sig.Exportable; -import org.bouncycastle.bcpg.sig.Features; -import org.bouncycastle.bcpg.sig.IntendedRecipientFingerprint; -import org.bouncycastle.bcpg.sig.IssuerFingerprint; -import org.bouncycastle.bcpg.sig.IssuerKeyID; -import org.bouncycastle.bcpg.sig.KeyExpirationTime; -import org.bouncycastle.bcpg.sig.KeyFlags; -import org.bouncycastle.bcpg.sig.NotationData; -import org.bouncycastle.bcpg.sig.PreferredAlgorithms; -import org.bouncycastle.bcpg.sig.PrimaryUserID; -import org.bouncycastle.bcpg.sig.RegularExpression; -import org.bouncycastle.bcpg.sig.Revocable; -import org.bouncycastle.bcpg.sig.RevocationKey; -import org.bouncycastle.bcpg.sig.RevocationReason; -import org.bouncycastle.bcpg.sig.SignatureCreationTime; -import org.bouncycastle.bcpg.sig.SignatureExpirationTime; -import org.bouncycastle.bcpg.sig.SignatureTarget; -import org.bouncycastle.bcpg.sig.SignerUserID; -import org.bouncycastle.bcpg.sig.TrustSignature; -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPPublicKey; -import org.bouncycastle.openpgp.PGPSignature; -import org.bouncycastle.openpgp.PGPSignatureList; -import org.bouncycastle.openpgp.PGPSignatureSubpacketVector; -import org.pgpainless.algorithm.CompressionAlgorithm; -import org.pgpainless.algorithm.Feature; -import org.pgpainless.algorithm.HashAlgorithm; -import org.pgpainless.algorithm.KeyFlag; -import org.pgpainless.algorithm.PublicKeyAlgorithm; -import org.pgpainless.algorithm.SignatureSubpacket; -import org.pgpainless.algorithm.SymmetricKeyAlgorithm; -import org.pgpainless.key.OpenPgpFingerprint; -import org.pgpainless.key.OpenPgpV4Fingerprint; -import org.pgpainless.key.generation.type.KeyType; -import org.pgpainless.signature.SignatureUtils; - -/** - * Utility class to access signature subpackets from signatures. - * - * Since rfc4880 is not always clear about where a signature subpacket can be located (hashed/unhashed area), - * this class makes some educated guesses as to where the subpacket may be found when necessary. - */ -public final class SignatureSubpacketsUtil { - - private SignatureSubpacketsUtil() { - - } - - /** - * Return the issuer-fingerprint subpacket of the signature. - * Since this packet is self-authenticating, we expect it to be in the unhashed area, - * however as it cannot hurt we search for it in the hashed area first. - * - * @param signature signature - * @return issuer fingerprint or null - */ - public static @Nullable IssuerFingerprint getIssuerFingerprint(PGPSignature signature) { - return hashedOrUnhashed(signature, SignatureSubpacket.issuerFingerprint); - } - - /** - * Return the {@link IssuerFingerprint} subpacket of the signature into a {@link org.pgpainless.key.OpenPgpFingerprint}. - * If no v4 issuer fingerprint is present in the signature, return null. - * - * @param signature signature - * @return v4 fingerprint of the issuer, or null - */ - public static @Nullable OpenPgpFingerprint getIssuerFingerprintAsOpenPgpFingerprint(PGPSignature signature) { - IssuerFingerprint subpacket = getIssuerFingerprint(signature); - if (subpacket == null) { - return null; - } - - OpenPgpFingerprint fingerprint = null; - if (subpacket.getKeyVersion() == 4) { - fingerprint = new OpenPgpV4Fingerprint(subpacket.getFingerprint()); - } - - return fingerprint; - } - - /** - * Return the issuer key-id subpacket of the signature. - * Since this packet is self-authenticating, we expect it to be in the unhashed area, - * however as it cannot hurt we search for it in the hashed area first. - * - * @param signature signature - * @return issuer key-id or null - */ - public static @Nullable IssuerKeyID getIssuerKeyId(PGPSignature signature) { - return hashedOrUnhashed(signature, SignatureSubpacket.issuerKeyId); - } - - /** - * Inspect the given signature's {@link IssuerKeyID} packet to determine the issuer key-id. - * If no such packet is present, return null. - * - * @param signature signature - * @return issuer key-id as {@link Long} - */ - public static @Nullable Long getIssuerKeyIdAsLong(PGPSignature signature) { - IssuerKeyID keyID = getIssuerKeyId(signature); - if (keyID == null) { - return null; - } - return keyID.getKeyID(); - } - - /** - * Return the revocation reason subpacket of the signature. - * Since this packet is rather important for revocations, we only search for it in the - * hashed area of the signature. - * - * @param signature signature - * @return revocation reason - */ - public static @Nullable RevocationReason getRevocationReason(PGPSignature signature) { - return hashed(signature, SignatureSubpacket.revocationReason); - } - - /** - * Return the signature creation time subpacket. - * Since this packet is rather important, we only search for it in the hashed area - * of the signature. - * - * @param signature signature - * @return signature creation time subpacket - */ - public static @Nullable SignatureCreationTime getSignatureCreationTime(PGPSignature signature) { - if (signature.getVersion() == 3) { - return new SignatureCreationTime(false, signature.getCreationTime()); - } - return hashed(signature, SignatureSubpacket.signatureCreationTime); - } - - /** - * Return the signature expiration time subpacket of the signature. - * Since this packet is rather important, we only search for it in the hashed area of the signature. - * - * @param signature signature - * @return signature expiration time - */ - public static @Nullable SignatureExpirationTime getSignatureExpirationTime(PGPSignature signature) { - return hashed(signature, SignatureSubpacket.signatureExpirationTime); - } - - /** - * Return the signatures' expiration time as a date. - * The expiration date is computed by adding the expiration time to the signature creation date. - * If the signature has no expiration time subpacket, or the expiration time is set to '0', this message returns null. - * - * @param signature signature - * @return expiration time as date - */ - public static @Nullable Date getSignatureExpirationTimeAsDate(PGPSignature signature) { - SignatureExpirationTime subpacket = getSignatureExpirationTime(signature); - if (subpacket == null) { - return null; - } - return SignatureUtils.datePlusSeconds(signature.getCreationTime(), subpacket.getTime()); - } - - /** - * Return the key expiration time subpacket of this signature. - * We only look for it in the hashed area of the signature. - * - * @param signature signature - * @return key expiration time - */ - public static @Nullable KeyExpirationTime getKeyExpirationTime(PGPSignature signature) { - return hashed(signature, SignatureSubpacket.keyExpirationTime); - } - - /** - * Return the signatures key-expiration time as a date. - * The expiration date is computed by adding the signatures' key-expiration time to the signing keys - * creation date. - * If the signature does not have a key-expiration time subpacket, or its value is '0', this method returns null. - * - * @param signature self-signature carrying the key-expiration time subpacket - * @param signingKey signature creation key - * @return key expiration time as date - */ - public static @Nullable Date getKeyExpirationTimeAsDate(PGPSignature signature, PGPPublicKey signingKey) { - if (signature.getKeyID() != signingKey.getKeyID()) { - throw new IllegalArgumentException("Provided key (" + Long.toHexString(signingKey.getKeyID()) + ") did not create the signature (" + Long.toHexString(signature.getKeyID()) + ")"); - } - KeyExpirationTime subpacket = getKeyExpirationTime(signature); - if (subpacket == null) { - return null; - } - - return SignatureUtils.datePlusSeconds(signingKey.getCreationTime(), subpacket.getTime()); - } - - /** - * Calculate the duration in seconds until the key expires after creation. - * - * @param expirationDate new expiration date - * @param creationDate key creation time - * @return lifetime of the key in seconds - */ - public static long getKeyLifetimeInSeconds(@Nullable Date expirationDate, @Nonnull Date creationDate) { - long secondsToExpire = 0; // 0 means "no expiration" - if (expirationDate != null) { - if (creationDate.after(expirationDate)) { - throw new IllegalArgumentException("Key MUST NOT expire before being created. " + - "(creation: " + creationDate + ", expiration: " + expirationDate + ")"); - } - secondsToExpire = (expirationDate.getTime() - creationDate.getTime()) / 1000; - } - return secondsToExpire; - } - - /** - * Return the revocable subpacket of this signature. - * We only look for it in the hashed area of the signature. - * - * @param signature signature - * @return revocable subpacket - */ - public static @Nullable Revocable getRevocable(PGPSignature signature) { - return hashed(signature, SignatureSubpacket.revocable); - } - - /** - * Return the symmetric algorithm preferences from the signatures hashed area. - * - * @param signature signature - * @return symm. algo. prefs - */ - public static @Nullable PreferredAlgorithms getPreferredSymmetricAlgorithms(PGPSignature signature) { - return hashed(signature, SignatureSubpacket.preferredSymmetricAlgorithms); - } - - /** - * Return the preferred {@link SymmetricKeyAlgorithm SymmetricKeyAlgorithms} as present in the signature. - * If no preference is given with regard to symmetric encryption algorithms, return an empty set. - * - * In any case, the resulting set is ordered by occurrence ({@link LinkedHashSet}). - * @param signature signature - * @return ordered set of symmetric key algorithm preferences - */ - public static @Nonnull Set parsePreferredSymmetricKeyAlgorithms(PGPSignature signature) { - Set algorithms = new LinkedHashSet<>(); - PreferredAlgorithms preferences = getPreferredSymmetricAlgorithms(signature); - if (preferences != null) { - for (int code : preferences.getPreferences()) { - SymmetricKeyAlgorithm algorithm = SymmetricKeyAlgorithm.fromId(code); - if (algorithm != null) { - algorithms.add(algorithm); - } - } - } - return algorithms; - } - - /** - * Return the hash algorithm preferences from the signatures hashed area. - * - * @param signature signature - * @return hash algo prefs - */ - public static @Nullable PreferredAlgorithms getPreferredHashAlgorithms(PGPSignature signature) { - return hashed(signature, SignatureSubpacket.preferredHashAlgorithms); - } - - /** - * Return the preferred {@link HashAlgorithm HashAlgorithms} as present in the signature. - * If no preference is given with regard to hash algorithms, return an empty set. - * - * In any case, the resulting set is ordered by occurrence ({@link LinkedHashSet}). - * @param signature signature - * @return ordered set of hash algorithm preferences - */ - public static @Nonnull Set parsePreferredHashAlgorithms(PGPSignature signature) { - Set algorithms = new LinkedHashSet<>(); - PreferredAlgorithms preferences = getPreferredHashAlgorithms(signature); - if (preferences != null) { - for (int code : preferences.getPreferences()) { - HashAlgorithm algorithm = HashAlgorithm.fromId(code); - if (algorithm != null) { - algorithms.add(algorithm); - } - } - } - return algorithms; - } - - /** - * Return the compression algorithm preferences from the signatures hashed area. - * - * @param signature signature - * @return compression algo prefs - */ - public static @Nullable PreferredAlgorithms getPreferredCompressionAlgorithms(PGPSignature signature) { - return hashed(signature, SignatureSubpacket.preferredCompressionAlgorithms); - } - - /** - * Return the preferred {@link CompressionAlgorithm CompressionAlgorithms} as present in the signature. - * If no preference is given with regard to compression algorithms, return an empty set. - * - * In any case, the resulting set is ordered by occurrence ({@link LinkedHashSet}). - * @param signature signature - * @return ordered set of compression algorithm preferences - */ - public static @Nonnull Set parsePreferredCompressionAlgorithms(PGPSignature signature) { - Set algorithms = new LinkedHashSet<>(); - PreferredAlgorithms preferences = getPreferredCompressionAlgorithms(signature); - if (preferences != null) { - for (int code : preferences.getPreferences()) { - CompressionAlgorithm algorithm = CompressionAlgorithm.fromId(code); - if (algorithm != null) { - algorithms.add(algorithm); - } - } - } - return algorithms; - } - - /** - * Return the primary user-id subpacket from the signatures hashed area. - * - * @param signature signature - * @return primary user id - */ - public static @Nullable PrimaryUserID getPrimaryUserId(PGPSignature signature) { - return hashed(signature, SignatureSubpacket.primaryUserId); - } - - /** - * Return the key flags subpacket from the signatures hashed area. - * - * @param signature signature - * @return key flags - */ - public static @Nullable KeyFlags getKeyFlags(PGPSignature signature) { - return hashed(signature, SignatureSubpacket.keyFlags); - } - - /** - * Return a list of key flags carried by the signature. - * If the signature is null, or has no {@link KeyFlags} subpacket, return null. - * - * @param signature signature - * @return list of key flags - */ - public static @Nullable List parseKeyFlags(@Nullable PGPSignature signature) { - if (signature == null) { - return null; - } - KeyFlags keyFlags = getKeyFlags(signature); - if (keyFlags == null) { - return null; - } - return KeyFlag.fromBitmask(keyFlags.getFlags()); - } - - /** - * Return the features subpacket from the signatures hashed area. - * - * @param signature signature - * @return features subpacket - */ - public static @Nullable Features getFeatures(PGPSignature signature) { - return hashed(signature, SignatureSubpacket.features); - } - - /** - * Parse out the features subpacket of a signature. - * If the signature has no features subpacket, return null. - * Otherwise, return the features as a feature set. - * - * @param signature signature - * @return features as set - */ - public static @Nullable Set parseFeatures(PGPSignature signature) { - Features features = getFeatures(signature); - if (features == null) { - return null; - } - return new LinkedHashSet<>(Feature.fromBitmask(features.getData()[0])); - } - - /** - * Return the signature target subpacket from the signature. - * We search for this subpacket in the hashed and unhashed area (in this order). - * - * @param signature signature - * @return signature target - */ - public static @Nullable SignatureTarget getSignatureTarget(PGPSignature signature) { - return hashedOrUnhashed(signature, SignatureSubpacket.signatureTarget); - } - - /** - * Return the notation data subpackets from the signatures hashed area. - * - * @param signature signature - * @return hashed notations - */ - public static @Nonnull List getHashedNotationData(PGPSignature signature) { - NotationData[] notations = signature.getHashedSubPackets().getNotationDataOccurrences(); - return Arrays.asList(notations); - } - - /** - * Return a list of all {@link NotationData} objects from the hashed area of the signature that have a - * notation name equal to the given notationName argument. - * - * @param signature signature - * @param notationName notation name - * @return list of matching notation data objects - */ - public static @Nonnull List getHashedNotationData(PGPSignature signature, String notationName) { - List allNotations = getHashedNotationData(signature); - List withName = new ArrayList<>(); - for (NotationData data : allNotations) { - if (data.getNotationName().equals(notationName)) { - withName.add(data); - } - } - return withName; - } - - /** - * Return the notation data subpackets from the signatures unhashed area. - * - * @param signature signature - * @return unhashed notations - */ - public static @Nonnull List getUnhashedNotationData(PGPSignature signature) { - NotationData[] notations = signature.getUnhashedSubPackets().getNotationDataOccurrences(); - return Arrays.asList(notations); - } - - /** - * Return a list of all {@link NotationData} objects from the unhashed area of the signature that have a - * notation name equal to the given notationName argument. - * - * @param signature signature - * @param notationName notation name - * @return list of matching notation data objects - */ - public static @Nonnull List getUnhashedNotationData(PGPSignature signature, String notationName) { - List allNotations = getUnhashedNotationData(signature); - List withName = new ArrayList<>(); - for (NotationData data : allNotations) { - if (data.getNotationName().equals(notationName)) { - withName.add(data); - } - } - return withName; - } - - /** - * Return the revocation key subpacket from the signatures hashed area. - * - * @param signature signature - * @return revocation key - */ - public static @Nullable RevocationKey getRevocationKey(PGPSignature signature) { - return hashed(signature, SignatureSubpacket.revocationKey); - } - - /** - * Return the signers user-id from the hashed area of the signature. - * TODO: Can this subpacket also be found in the unhashed area? - * - * @param signature signature - * @return signers user-id - */ - public static @Nullable SignerUserID getSignerUserID(PGPSignature signature) { - return hashed(signature, SignatureSubpacket.signerUserId); - } - - /** - * Return the intended recipients fingerprint subpackets from the hashed area of this signature. - * - * @param signature signature - * @return intended recipient fingerprint subpackets - */ - public static @Nonnull List getIntendedRecipientFingerprints(PGPSignature signature) { - org.bouncycastle.bcpg.SignatureSubpacket[] subpackets = signature.getHashedSubPackets().getSubpackets(SignatureSubpacket.intendedRecipientFingerprint.getCode()); - List intendedRecipients = new ArrayList<>(subpackets.length); - for (org.bouncycastle.bcpg.SignatureSubpacket subpacket : subpackets) { - intendedRecipients.add((IntendedRecipientFingerprint) subpacket); - } - return intendedRecipients; - } - - /** - * Return the embedded signature subpacket from the signatures hashed area. - * - * @param signature signature - * @return embedded signature - * - * @throws PGPException in case the embedded signatures cannot be parsed - */ - public static @Nullable PGPSignatureList getEmbeddedSignature(PGPSignature signature) throws PGPException { - PGPSignatureList hashed = signature.getHashedSubPackets().getEmbeddedSignatures(); - if (!hashed.isEmpty()) { - return hashed; - } - return signature.getUnhashedSubPackets().getEmbeddedSignatures(); - } - - /** - * Return the signatures exportable certification subpacket from the hashed area. - * - * @param signature signature - * @return exportable certification subpacket - */ - public static @Nullable Exportable getExportableCertification(PGPSignature signature) { - return hashed(signature, SignatureSubpacket.exportableCertification); - } - - public static boolean isExportable(PGPSignature signature) { - Exportable exportable = getExportableCertification(signature); - return exportable == null || exportable.isExportable(); - } - - /** - * Return the trust signature packet from the signatures hashed area. - * - * @param signature signature - * @return trust signature subpacket - */ - public static @Nullable TrustSignature getTrustSignature(PGPSignature signature) { - return hashed(signature, SignatureSubpacket.trustSignature); - } - - public static int getTrustDepthOr(PGPSignature signature, int defaultDepth) { - TrustSignature packet = getTrustSignature(signature); - if (packet != null) { - return packet.getDepth(); - } - return defaultDepth; - } - - public static int getTrustAmountOr(PGPSignature signature, int defaultAmount) { - TrustSignature packet = getTrustSignature(signature); - if (packet != null) { - return packet.getTrustAmount(); - } - return defaultAmount; - } - - /** - * Return all regular expression subpackets from the hashed area of the given signature. - * - * @param signature signature - * @return list of regular expressions - */ - public static List getRegularExpressions(PGPSignature signature) { - org.bouncycastle.bcpg.SignatureSubpacket[] subpackets = signature.getHashedSubPackets() - .getSubpackets(SignatureSubpacket.regularExpression.getCode()); - List regularExpressions = new ArrayList<>(subpackets.length); - for (org.bouncycastle.bcpg.SignatureSubpacket subpacket : subpackets) { - regularExpressions.add((RegularExpression) subpacket); - } - return regularExpressions; - } - - - /** - * Select a list of all signature subpackets of the given type, which are present in the hashed area of - * the given signature. - * - * @param signature signature - * @param type subpacket type - * @param

generic subpacket type - * @return list of subpackets from the hashed area - */ - private static @Nullable

P hashed(PGPSignature signature, SignatureSubpacket type) { - return getSignatureSubpacket(signature.getHashedSubPackets(), type); - } - - /** - * Select a list of all signature subpackets of the given type, which are present in the unhashed area of - * the given signature. - * - * @param signature signature - * @param type subpacket type - * @param

generic subpacket type - * @return list of subpackets from the unhashed area - */ - private static @Nullable

P unhashed(PGPSignature signature, SignatureSubpacket type) { - return getSignatureSubpacket(signature.getUnhashedSubPackets(), type); - } - - /** - * Select a list of all signature subpackets of the given type, which are present in either the hashed - * or the unhashed area of the given signature. - * - * @param signature signature - * @param type subpacket type - * @param

generic subpacket type - * @return list of subpackets from the hashed/unhashed area - */ - private static @Nullable

P hashedOrUnhashed(PGPSignature signature, SignatureSubpacket type) { - P hashedSubpacket = hashed(signature, type); - return hashedSubpacket != null ? hashedSubpacket : unhashed(signature, type); - } - - /** - * Return the last occurrence of a subpacket type in the given signature subpacket vector. - * - * @param vector subpacket vector (hashed/unhashed) - * @param type subpacket type - * @param

generic return type of the subpacket - * @return last occurrence of the subpacket in the vector - */ - public static @Nullable

P getSignatureSubpacket(PGPSignatureSubpacketVector vector, SignatureSubpacket type) { - if (vector == null) { - // Almost never happens, but may be caused by broken signatures. - return null; - } - org.bouncycastle.bcpg.SignatureSubpacket[] allPackets = vector.getSubpackets(type.getCode()); - if (allPackets.length == 0) { - return null; - } - - org.bouncycastle.bcpg.SignatureSubpacket last = allPackets[allPackets.length - 1]; - return (P) last; - } - - /** - * Make sure that the given key type can carry the given key flags. - * - * @param type key type - * @param flags key flags - */ - public static void assureKeyCanCarryFlags(KeyType type, KeyFlag... flags) { - final int mask = KeyFlag.toBitmask(flags); - - if (!type.canCertify() && KeyFlag.hasKeyFlag(mask, KeyFlag.CERTIFY_OTHER)) { - throw new IllegalArgumentException("KeyType " + type.getName() + " cannot carry key flag CERTIFY_OTHER."); - } - - if (!type.canSign() && KeyFlag.hasKeyFlag(mask, KeyFlag.SIGN_DATA)) { - throw new IllegalArgumentException("KeyType " + type.getName() + " cannot carry key flag SIGN_DATA."); - } - - if (!type.canEncryptCommunication() && KeyFlag.hasKeyFlag(mask, KeyFlag.ENCRYPT_COMMS)) { - throw new IllegalArgumentException("KeyType " + type.getName() + " cannot carry key flag ENCRYPT_COMMS."); - } - - if (!type.canEncryptStorage() && KeyFlag.hasKeyFlag(mask, KeyFlag.ENCRYPT_STORAGE)) { - throw new IllegalArgumentException("KeyType " + type.getName() + " cannot carry key flag ENCRYPT_STORAGE."); - } - - if (!type.canAuthenticate() && KeyFlag.hasKeyFlag(mask, KeyFlag.AUTHENTICATION)) { - throw new IllegalArgumentException("KeyType " + type.getName() + " cannot carry key flag AUTHENTICATION."); - } - } - - /** - * Make sure that a key of the given {@link PublicKeyAlgorithm} is able to carry the given key flags. - * - * @param algorithm key algorithm - * @param flags key flags - */ - public static void assureKeyCanCarryFlags(PublicKeyAlgorithm algorithm, KeyFlag... flags) { - final int mask = KeyFlag.toBitmask(flags); - - if (!algorithm.isSigningCapable() && KeyFlag.hasKeyFlag(mask, KeyFlag.CERTIFY_OTHER)) { - throw new IllegalArgumentException("Algorithm " + algorithm + " cannot be used with key flag CERTIFY_OTHER."); - } - - if (!algorithm.isSigningCapable() && KeyFlag.hasKeyFlag(mask, KeyFlag.SIGN_DATA)) { - throw new IllegalArgumentException("Algorithm " + algorithm + " cannot be used with key flag SIGN_DATA."); - } - - if (!algorithm.isEncryptionCapable() && KeyFlag.hasKeyFlag(mask, KeyFlag.ENCRYPT_COMMS)) { - throw new IllegalArgumentException("Algorithm " + algorithm + " cannot be used with key flag ENCRYPT_COMMS."); - } - - if (!algorithm.isEncryptionCapable() && KeyFlag.hasKeyFlag(mask, KeyFlag.ENCRYPT_STORAGE)) { - throw new IllegalArgumentException("Algorithm " + algorithm + " cannot be used with key flag ENCRYPT_STORAGE."); - } - - if (!algorithm.isSigningCapable() && KeyFlag.hasKeyFlag(mask, KeyFlag.AUTHENTICATION)) { - throw new IllegalArgumentException("Algorithm " + algorithm + " cannot be used with key flag AUTHENTICATION."); - } - } -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpacketsUtil.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpacketsUtil.kt new file mode 100644 index 00000000..23fe716c --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpacketsUtil.kt @@ -0,0 +1,575 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.signature.subpackets + +import org.bouncycastle.bcpg.sig.* +import org.bouncycastle.openpgp.PGPPublicKey +import org.bouncycastle.openpgp.PGPSignature +import org.bouncycastle.openpgp.PGPSignatureList +import org.bouncycastle.openpgp.PGPSignatureSubpacketVector +import org.pgpainless.algorithm.* +import org.pgpainless.algorithm.KeyFlag.Companion.hasKeyFlag +import org.pgpainless.algorithm.KeyFlag.Companion.toBitmask +import org.pgpainless.key.OpenPgpFingerprint +import org.pgpainless.key.OpenPgpV4Fingerprint +import org.pgpainless.key.OpenPgpV5Fingerprint +import org.pgpainless.key.OpenPgpV6Fingerprint +import org.pgpainless.key.generation.type.KeyType +import org.pgpainless.key.util.KeyIdUtil +import org.pgpainless.signature.SignatureUtils +import java.util.* + +class SignatureSubpacketsUtil { + companion object { + + /** + * Return the issuer-fingerprint subpacket of the signature. + * Since this packet is self-authenticating, we expect it to be in the unhashed area, + * however as it cannot hurt we search for it in the hashed area first. + * + * @param signature signature + * @return issuer fingerprint or null + */ + @JvmStatic + fun getIssuerFingerprint(signature: PGPSignature): IssuerFingerprint? = + hashedOrUnhashed(signature, SignatureSubpacket.issuerFingerprint) + + /** + * Return the [IssuerFingerprint] subpacket of the signature into a [org.pgpainless.key.OpenPgpFingerprint]. + * If no v4, v5 or v6 issuer fingerprint is present in the signature, return null. + * + * @param signature signature + * @return fingerprint of the issuer, or null + */ + @JvmStatic + fun getIssuerFingerprintAsOpenPgpFingerprint(signature: PGPSignature): OpenPgpFingerprint? { + val subpacket = getIssuerFingerprint(signature) ?: return null + return when(subpacket.keyVersion) { + 4 -> OpenPgpV4Fingerprint(subpacket.fingerprint) + 5 -> OpenPgpV5Fingerprint(subpacket.fingerprint) + 6 -> OpenPgpV6Fingerprint(subpacket.fingerprint) + else -> null + } + } + + @JvmStatic + fun getIssuerKeyId(signature: PGPSignature): IssuerKeyID? = + hashedOrUnhashed(signature, SignatureSubpacket.issuerKeyId) + + /** + * Inspect the given signature's [IssuerKeyID] packet to determine the issuer key-id. + * If no such packet is present, return null. + * + * @param signature signature + * @return issuer key-id as {@link Long} + */ + @JvmStatic + fun getIssuerKeyIdAsLong(signature: PGPSignature): Long? = + getIssuerKeyId(signature)?.keyID + + /** + * Return the revocation reason subpacket of the signature. + * Since this packet is rather important for revocations, we only search for it in the + * hashed area of the signature. + * + * @param signature signature + * @return revocation reason + */ + @JvmStatic + fun getRevocationReason(signature: PGPSignature): RevocationReason? = + hashed(signature, SignatureSubpacket.revocationReason) + + /** + * Return the signature creation time subpacket. + * Since this packet is rather important, we only search for it in the hashed area + * of the signature. + * + * @param signature signature + * @return signature creation time subpacket + */ + @JvmStatic + fun getSignatureCreationTime(signature: PGPSignature): SignatureCreationTime? = + if (signature.version == 3) SignatureCreationTime(false, signature.creationTime) + else hashed(signature, SignatureSubpacket.signatureCreationTime) + + /** + * Return the signature expiration time subpacket of the signature. + * Since this packet is rather important, we only search for it in the hashed area of the signature. + * + * @param signature signature + * @return signature expiration time + */ + @JvmStatic + fun getSignatureExpirationTime(signature: PGPSignature): SignatureExpirationTime? = + hashed(signature, SignatureSubpacket.signatureExpirationTime) + + /** + * Return the signatures' expiration time as a date. + * The expiration date is computed by adding the expiration time to the signature creation date. + * If the signature has no expiration time subpacket, or the expiration time is set to '0', this message returns null. + * + * @param signature signature + * @return expiration time as date + */ + @JvmStatic + fun getSignatureExpirationTimeAsDate(signature: PGPSignature): Date? = + getSignatureExpirationTime(signature)?.let { + SignatureUtils.datePlusSeconds(signature.creationTime, it.time) + } + + /** + * Return the key expiration time subpacket of this signature. + * We only look for it in the hashed area of the signature. + * + * @param signature signature + * @return key expiration time + */ + @JvmStatic + fun getKeyExpirationTime(signature: PGPSignature): KeyExpirationTime? = + hashed(signature, SignatureSubpacket.keyExpirationTime) + + /** + * Return the signatures key-expiration time as a date. + * The expiration date is computed by adding the signatures' key-expiration time to the signing keys + * creation date. + * If the signature does not have a key-expiration time subpacket, or its value is '0', this method returns null. + * + * @param signature self-signature carrying the key-expiration time subpacket + * @param signingKey signature creation key + * @return key expiration time as date + */ + @JvmStatic + fun getKeyExpirationTimeAsDate(signature: PGPSignature, signingKey: PGPPublicKey): Date? = + require(signature.keyID == signingKey.keyID) { + "Provided key (${KeyIdUtil.formatKeyId(signingKey.keyID)}) did not create the signature (${KeyIdUtil.formatKeyId(signature.keyID)})" + }.run { + getKeyExpirationTime(signature)?.let { + SignatureUtils.datePlusSeconds(signingKey.creationTime, it.time) + } + } + + /** + * Calculate the duration in seconds until the key expires after creation. + * + * @param expirationTime new expiration date + * @param creationTime key creation time + * @return lifetime of the key in seconds + */ + @JvmStatic + fun getKeyLifetimeInSeconds(creationTime: Date, expirationTime: Date?): Long = + expirationTime?.let { + require(creationTime <= it) { + "Key MUST NOT expire before being created.\n" + + "(creation: $creationTime, expiration: $it)" + }.run { + (it.time - creationTime.time) / 1000 + } + } ?: 0 // 0 means "no expiration" + + /** + * Return the revocable subpacket of this signature. + * We only look for it in the hashed area of the signature. + * + * @param signature signature + * @return revocable subpacket + */ + @JvmStatic + fun getRevocable(signature: PGPSignature): Revocable? = + hashed(signature, SignatureSubpacket.revocable) + + /** + * Return the symmetric algorithm preferences from the signatures hashed area. + * + * @param signature signature + * @return symm. algo. prefs + */ + @JvmStatic + fun getPreferredSymmetricAlgorithms(signature: PGPSignature): PreferredAlgorithms? = + hashed(signature, SignatureSubpacket.preferredSymmetricAlgorithms) + + /** + * Return the preferred [SymmetricKeyAlgorithms][SymmetricKeyAlgorithm] as present in the signature. + * If no preference is given with regard to symmetric encryption algorithms, return an empty set. + * + * In any case, the resulting set is ordered by occurrence. + * @param signature signature + * @return ordered set of symmetric key algorithm preferences + */ + @JvmStatic + fun parsePreferredSymmetricKeyAlgorithms(signature: PGPSignature): Set = + getPreferredSymmetricAlgorithms(signature) + ?.preferences + ?.map { SymmetricKeyAlgorithm.fromId(it) } + ?.filterNotNull() + ?.toSet() ?: setOf() + + /** + * Return the hash algorithm preferences from the signatures hashed area. + * + * @param signature signature + * @return hash algo prefs + */ + @JvmStatic + fun getPreferredHashAlgorithms(signature: PGPSignature): PreferredAlgorithms? = + hashed(signature, SignatureSubpacket.preferredHashAlgorithms) + + /** + * Return the preferred [HashAlgorithms][HashAlgorithm] as present in the signature. + * If no preference is given with regard to hash algorithms, return an empty set. + * + * In any case, the resulting set is ordered by occurrence. + * @param signature signature + * @return ordered set of hash algorithm preferences + */ + @JvmStatic + fun parsePreferredHashAlgorithms(signature: PGPSignature): Set = + getPreferredHashAlgorithms(signature) + ?.preferences + ?.map { HashAlgorithm.fromId(it) } + ?.filterNotNull() + ?.toSet() ?: setOf() + + /** + * Return the compression algorithm preferences from the signatures hashed area. + * + * @param signature signature + * @return compression algo prefs + */ + @JvmStatic + fun getPreferredCompressionAlgorithms(signature: PGPSignature): PreferredAlgorithms? = + hashed(signature, SignatureSubpacket.preferredCompressionAlgorithms) + + /** + * Return the preferred [CompressionAlgorithms][CompressionAlgorithm] as present in the signature. + * If no preference is given with regard to compression algorithms, return an empty set. + * + * In any case, the resulting set is ordered by occurrence. + * @param signature signature + * @return ordered set of compression algorithm preferences + */ + @JvmStatic + fun parsePreferredCompressionAlgorithms(signature: PGPSignature): Set = + getPreferredCompressionAlgorithms(signature) + ?.preferences + ?.map { CompressionAlgorithm.fromId(it) } + ?.filterNotNull() + ?.toSet() ?: setOf() + + @JvmStatic + fun getPreferredAeadAlgorithms(signature: PGPSignature): PreferredAEADCiphersuites? = + hashed(signature, SignatureSubpacket.preferredAEADAlgorithms) + + /** + * Return the primary user-id subpacket from the signatures hashed area. + * + * @param signature signature + * @return primary user id + */ + @JvmStatic + fun getPrimaryUserId(signature: PGPSignature): PrimaryUserID? = + hashed(signature, SignatureSubpacket.primaryUserId) + + /** + * Return the key flags subpacket from the signatures hashed area. + * + * @param signature signature + * @return key flags + */ + @JvmStatic + fun getKeyFlags(signature: PGPSignature): KeyFlags? = + hashed(signature, SignatureSubpacket.keyFlags) + + /** + * Return a list of key flags carried by the signature. + * If the signature is null, or has no [KeyFlags] subpacket, return null. + * + * @param signature signature + * @return list of key flags + */ + @JvmStatic + fun parseKeyFlags(signature: PGPSignature?): List? = + signature?.let { sig -> + getKeyFlags(sig)?.let { + KeyFlag.fromBitmask(it.flags) + } + } + + /** + * Return the features subpacket from the signatures hashed area. + * + * @param signature signature + * @return features subpacket + */ + @JvmStatic + fun getFeatures(signature: PGPSignature): Features? = + hashed(signature, SignatureSubpacket.features) + + /** + * Parse out the features subpacket of a signature. + * If the signature has no features subpacket, return null. + * Otherwise, return the features as a feature set. + * + * @param signature signature + * @return features as set + */ + @JvmStatic + fun parseFeatures(signature: PGPSignature): Set? = + getFeatures(signature)?.let { + Feature.fromBitmask(it.features.toInt()).toSet() + } + + /** + * Return the signature target subpacket from the signature. + * We search for this subpacket in the hashed and unhashed area (in this order). + * + * @param signature signature + * @return signature target + */ + @JvmStatic + fun getSignatureTarget(signature: PGPSignature): SignatureTarget? = + hashedOrUnhashed(signature, SignatureSubpacket.signatureTarget) + + /** + * Return the notation data subpackets from the signatures hashed area. + * + * @param signature signature + * @return hashed notations + */ + @JvmStatic + fun getHashedNotationData(signature: PGPSignature): List = + signature.hashedSubPackets.notationDataOccurrences.toList() + + /** + * Return a list of all [NotationData] objects from the hashed area of the signature that have a + * notation name equal to the given notationName argument. + * + * @param signature signature + * @param notationName notation name + * @return list of matching notation data objects + */ + @JvmStatic + fun getHashedNotationData(signature: PGPSignature, notationName: String): List = + getHashedNotationData(signature) + .filter { it.notationName == notationName } + + /** + * Return the notation data subpackets from the signatures unhashed area. + * + * @param signature signature + * @return unhashed notations + */ + @JvmStatic + fun getUnhashedNotationData(signature: PGPSignature): List = + signature.unhashedSubPackets.notationDataOccurrences.toList() + + /** + * Return a list of all [NotationData] objects from the unhashed area of the signature that have a + * notation name equal to the given notationName argument. + * + * @param signature signature + * @param notationName notation name + * @return list of matching notation data objects + */ + @JvmStatic + fun getUnhashedNotationData(signature: PGPSignature, notationName: String) = + getUnhashedNotationData(signature) + .filter { it.notationName == notationName } + + /** + * Return the revocation key subpacket from the signatures hashed area. + * + * @param signature signature + * @return revocation key + */ + @JvmStatic + fun getRevocationKey(signature: PGPSignature): RevocationKey? = + hashed(signature, SignatureSubpacket.revocationKey) + + /** + * Return the signers user-id from the hashed area of the signature. + * TODO: Can this subpacket also be found in the unhashed area? + * + * @param signature signature + * @return signers user-id + */ + @JvmStatic + fun getSignerUserID(signature: PGPSignature): SignerUserID? = + hashed(signature, SignatureSubpacket.signerUserId) + + /** + * Return the intended recipients fingerprint subpackets from the hashed area of this signature. + * + * @param signature signature + * @return intended recipient fingerprint subpackets + */ + @JvmStatic + fun getIntendedRecipientFingerprints(signature: PGPSignature): List = + signature.hashedSubPackets.intendedRecipientFingerprints.toList() + + /** + * Return the embedded signature subpacket from the signatures hashed area or unhashed area. + * + * @param signature signature + * @return embedded signature + */ + @JvmStatic + fun getEmbeddedSignature(signature: PGPSignature): PGPSignatureList = + signature.hashedSubPackets.embeddedSignatures.let { + if (it.isEmpty) signature.unhashedSubPackets.embeddedSignatures + else it + } + + /** + * Return the signatures exportable certification subpacket from the hashed area. + * + * @param signature signature + * @return exportable certification subpacket + */ + @JvmStatic + fun getExportableCertification(signature: PGPSignature): Exportable? = + hashed(signature, SignatureSubpacket.exportableCertification) + + /** + * Return true, if the signature is not explicitly marked as non-exportable. + */ + @JvmStatic + fun isExportable(signature: PGPSignature): Boolean = + getExportableCertification(signature)?.isExportable ?: true + + /** + * Return the trust signature packet from the signatures hashed area. + * + * @param signature signature + * @return trust signature subpacket + */ + @JvmStatic + fun getTrustSignature(signature: PGPSignature): TrustSignature? = + hashed(signature, SignatureSubpacket.trustSignature) + + /** + * Return the trust depth set in the signatures [TrustSignature] packet, or [defaultDepth] if no such packet + * is found. + * + * @param signature signature + * @param defaultDepth default value that is returned if no trust signature packet is found + * @return depth or default depth + */ + @JvmStatic + fun getTrustDepthOr(signature: PGPSignature, defaultDepth: Int): Int = + getTrustSignature(signature)?.depth ?: defaultDepth + + /** + * Return the trust amount set in the signatures [TrustSignature] packet, or [defaultAmount] if no such packet + * is found. + * + * @param signature signature + * @param defaultAmount default value that is returned if no trust signature packet is found + * @return amount or default amount + */ + @JvmStatic + fun getTrustAmountOr(signature: PGPSignature, defaultAmount: Int): Int = + getTrustSignature(signature)?.trustAmount ?: defaultAmount + + /** + * Return all regular expression subpackets from the hashed area of the given signature. + * + * @param signature signature + * @return list of regular expressions + */ + @JvmStatic + fun getRegularExpressions(signature: PGPSignature): List = + signature.hashedSubPackets.regularExpressions.toList() + + /** + * Select a list of all signature subpackets of the given type, which are present in either the hashed + * or the unhashed area of the given signature. + * + * @param signature signature + * @param type subpacket type + * @param

generic subpacket type + * @return list of subpackets from the hashed/unhashed area + */ + @JvmStatic + fun

hashedOrUnhashed(signature: PGPSignature, type: SignatureSubpacket): P? { + return hashed(signature, type) ?: unhashed(signature, type) + } + + /** + * Select a list of all signature subpackets of the given type, which are present in the hashed area of + * the given signature. + * + * @param signature signature + * @param type subpacket type + * @param

generic subpacket type + * @return list of subpackets from the hashed area + */ + @JvmStatic + fun

hashed(signature: PGPSignature, type: SignatureSubpacket): P? { + return getSignatureSubpacket(signature.hashedSubPackets, type) + } + + /** + * Select a list of all signature subpackets of the given type, which are present in the unhashed area of + * the given signature. + * + * @param signature signature + * @param type subpacket type + * @param

generic subpacket type + * @return list of subpackets from the unhashed area + */ + @JvmStatic + fun

unhashed(signature: PGPSignature, type: SignatureSubpacket): P? { + return getSignatureSubpacket(signature.unhashedSubPackets, type) + } + + /** + * Return the last occurrence of a subpacket type in the given signature subpacket vector. + * + * @param vector subpacket vector (hashed/unhashed) + * @param type subpacket type + * @param

generic return type of the subpacket + * @return last occurrence of the subpacket in the vector + */ + @JvmStatic + fun

getSignatureSubpacket(vector: PGPSignatureSubpacketVector?, type: SignatureSubpacket): P? { + val allPackets = vector?.getSubpackets(type.code) ?: return null + return if (allPackets.isEmpty()) + null + else + @Suppress("UNCHECKED_CAST") + allPackets.last() as P + } + + @JvmStatic + fun assureKeyCanCarryFlags(type: KeyType, vararg flags: KeyFlag) { + assureKeyCanCarryFlags(type.algorithm, *flags) + } + + @JvmStatic + fun assureKeyCanCarryFlags(algorithm: PublicKeyAlgorithm, vararg flags: KeyFlag) { + val mask = toBitmask(*flags) + + if (!algorithm.isSigningCapable() && hasKeyFlag(mask, KeyFlag.CERTIFY_OTHER)) { + throw IllegalArgumentException("Algorithm $algorithm cannot be used with key flag CERTIFY_OTHER.") + } + + if (!algorithm.isSigningCapable() && hasKeyFlag(mask, KeyFlag.SIGN_DATA)) { + throw IllegalArgumentException("Algorithm $algorithm cannot be used with key flag SIGN_DATA.") + } + + if (!algorithm.isEncryptionCapable() && hasKeyFlag(mask, KeyFlag.ENCRYPT_COMMS)) { + throw IllegalArgumentException("Algorithm $algorithm cannot be used with key flag ENCRYPT_COMMS.") + } + + if (!algorithm.isEncryptionCapable() && hasKeyFlag(mask, KeyFlag.ENCRYPT_STORAGE)) { + throw IllegalArgumentException("Algorithm $algorithm cannot be used with key flag ENCRYPT_STORAGE.") + } + + if (!algorithm.isSigningCapable() && hasKeyFlag(mask, KeyFlag.AUTHENTICATION)) { + throw IllegalArgumentException("Algorithm $algorithm cannot be used with key flag AUTHENTICATION.") + } + } + } +} \ No newline at end of file