001// SPDX-FileCopyrightText: 2020 Paul Schaub <vanitasvitae@fsfe.org>
002//
003// SPDX-License-Identifier: Apache-2.0
004
005package org.pgpainless.key.util;
006
007import java.util.ArrayList;
008import java.util.Collections;
009import java.util.Date;
010import java.util.Iterator;
011import java.util.LinkedHashSet;
012import java.util.List;
013import java.util.Set;
014
015import org.bouncycastle.openpgp.PGPPublicKey;
016import org.bouncycastle.openpgp.PGPSignature;
017import org.pgpainless.algorithm.HashAlgorithm;
018import org.pgpainless.algorithm.SignatureType;
019
020public final class OpenPgpKeyAttributeUtil {
021
022    private OpenPgpKeyAttributeUtil() {
023
024    }
025
026    public static List<HashAlgorithm> getPreferredHashAlgorithms(PGPPublicKey publicKey) {
027        List<HashAlgorithm> hashAlgorithms = new ArrayList<>();
028        Iterator<?> keySignatures = publicKey.getSignatures();
029        while (keySignatures.hasNext()) {
030            PGPSignature signature = (PGPSignature) keySignatures.next();
031
032            if (signature.getKeyID() != publicKey.getKeyID()) {
033                // Signature from a foreign key. Skip.
034                continue;
035            }
036
037            SignatureType signatureType = SignatureType.valueOf(signature.getSignatureType());
038            if (signatureType == SignatureType.POSITIVE_CERTIFICATION
039                    || signatureType == SignatureType.GENERIC_CERTIFICATION) {
040                int[] hashAlgos = signature.getHashedSubPackets().getPreferredHashAlgorithms();
041                if (hashAlgos == null) {
042                    continue;
043                }
044                for (int h : hashAlgos) {
045                    hashAlgorithms.add(HashAlgorithm.fromId(h));
046                }
047                // Exit the loop after the first key signature with hash algorithms.
048                break;
049            }
050        }
051        return hashAlgorithms;
052    }
053
054    /**
055     * Return the hash algorithm that was used in the latest self signature.
056     *
057     * @param publicKey public key
058     * @return list of hash algorithm
059     */
060    public static List<HashAlgorithm> guessPreferredHashAlgorithms(PGPPublicKey publicKey) {
061        HashAlgorithm hashAlgorithm = null;
062        Date lastCreationDate = null;
063
064        Iterator<?> keySignatures = publicKey.getSignatures();
065        while (keySignatures.hasNext()) {
066            PGPSignature signature = (PGPSignature) keySignatures.next();
067            if (signature.getKeyID() != publicKey.getKeyID()) {
068                continue;
069            }
070
071            SignatureType signatureType = SignatureType.valueOf(signature.getSignatureType());
072            if (signatureType != SignatureType.POSITIVE_CERTIFICATION
073                    && signatureType != SignatureType.GENERIC_CERTIFICATION) {
074                continue;
075            }
076
077            Date creationDate = signature.getCreationTime();
078            if (lastCreationDate == null || lastCreationDate.before(creationDate)) {
079                lastCreationDate = creationDate;
080                hashAlgorithm = HashAlgorithm.fromId(signature.getHashAlgorithm());
081            }
082        }
083
084        if (hashAlgorithm == null) {
085            return Collections.emptyList();
086        }
087        return Collections.singletonList(hashAlgorithm);
088    }
089
090    /**
091     * Try to extract hash algorithm preferences from self signatures.
092     * If no self-signature containing hash algorithm preferences is found,
093     * try to derive a hash algorithm preference by inspecting the hash algorithm used by existing
094     * self-signatures.
095     *
096     * @param publicKey key
097     * @return hash algorithm preferences (might be empty!)
098     */
099    public static Set<HashAlgorithm> getOrGuessPreferredHashAlgorithms(PGPPublicKey publicKey) {
100        List<HashAlgorithm> preferredHashAlgorithms = OpenPgpKeyAttributeUtil.getPreferredHashAlgorithms(publicKey);
101        if (preferredHashAlgorithms.isEmpty()) {
102            preferredHashAlgorithms = OpenPgpKeyAttributeUtil.guessPreferredHashAlgorithms(publicKey);
103        }
104        return new LinkedHashSet<>(preferredHashAlgorithms);
105    }
106}