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}