Attempt to deal with multiple issuer ids/fps

This commit is contained in:
Paul Schaub 2022-04-12 22:35:42 +02:00
parent c39d5a09ce
commit 9f7d4d165e
Signed by: vanitasvitae
GPG Key ID: 62BEE9264BF17311
5 changed files with 251 additions and 61 deletions

View File

@ -110,14 +110,31 @@ public final class DecryptionStreamFactory {
private void initializeDetachedSignatures(Set<PGPSignature> signatures) {
for (PGPSignature signature : signatures) {
long issuerKeyId = SignatureUtils.determineIssuerKeyId(signature);
PGPPublicKeyRing signingKeyRing = findSignatureVerificationKeyRing(issuerKeyId);
List<Long> issuers = SignatureUtils.determineSignatureIssuers(signature);
if (issuers.isEmpty()) {
resultBuilder.addInvalidDetachedSignature(new SignatureVerification(signature, null),
new SignatureValidationException("Signature does not contain issuer information."));
continue;
}
PGPPublicKeyRing signingKeyRing = null;
long issuerKeyId = 0;
for (long candidate : issuers) {
issuerKeyId = candidate;
signingKeyRing = findSignatureVerificationKeyRing(issuerKeyId);
if (signingKeyRing != null) {
break;
}
}
if (signingKeyRing == null) {
SignatureValidationException ex = new SignatureValidationException(
"Missing verification certificate " + Long.toHexString(issuerKeyId));
"Missing verification certificate " + Long.toHexString(issuers.get(0)));
resultBuilder.addInvalidDetachedSignature(new SignatureVerification(signature, null), ex);
continue;
}
PGPPublicKey signingKey = signingKeyRing.getPublicKey(issuerKeyId);
SubkeyIdentifier signingKeyIdentifier = new SubkeyIdentifier(signingKeyRing, signingKey.getKeyID());
try {

View File

@ -10,6 +10,7 @@ import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@ -296,6 +297,15 @@ public final class SignatureUtils {
return 0;
}
public static List<Long> determineSignatureIssuers(PGPSignature signature) {
Set<Long> issuers = new HashSet<>(SignatureSubpacketsUtil.getIssuerKeyIdsAsLongs(signature));
List<OpenPgpFingerprint> fingerprints = SignatureSubpacketsUtil.getIssuerFingerprintsAsOpenPgpFingerprints(signature);
for (OpenPgpFingerprint fingerprint : fingerprints) {
issuers.add(fingerprint.getKeyId());
}
return new ArrayList<>(issuers);
}
/**
* Return the digest prefix of the signature as hex-encoded String.
*

View File

@ -60,27 +60,35 @@ public abstract class SignatureValidator {
OpenPgpFingerprint signingKeyFingerprint = OpenPgpFingerprint.of(signingKey);
List<Long> issuers = SignatureSubpacketsUtil.getIssuerKeyIdsAsLongs(signature);
boolean match = false;
for (Long issuer : issuers) {
if (issuer == 0L || issuer == signingKey.getKeyID()) {
match = true;
break;
// Match or wildcard issuer
return;
}
}
if (!match) {
List<OpenPgpFingerprint> fingerprints = SignatureSubpacketsUtil.getIssuerFingerprintsAsOpenPgpFingerprints(signature);
for (OpenPgpFingerprint fingerprint : fingerprints) {
if (fingerprint.equals(signingKeyFingerprint)) {
// Match
return;
}
}
// There were only mismatching fingerprints
if (!fingerprints.isEmpty()) {
throw new SignatureValidationException("Signature was not created by " + signingKeyFingerprint +
" (signature fingerprints: " + Arrays.toString(fingerprints.toArray(new OpenPgpFingerprint[0])) + ")");
}
// There were only mismatching issuers
if (!issuers.isEmpty()) {
String[] hex = new String[issuers.size()];
for (int i = 0; i < hex.length; i++) {
hex[i] = Long.toHexString(issuers.get(i));
}
throw new SignatureValidationException("Signature was not created by " + signingKeyFingerprint + " (signature issuers: " + Arrays.toString(hex) + ")");
}
OpenPgpFingerprint fingerprint = SignatureSubpacketsUtil.getIssuerFingerprintAsOpenPgpFingerprint(signature);
if (fingerprint != null) {
if (!fingerprint.equals(signingKeyFingerprint)) {
throw new SignatureValidationException("Signature was not created by " + signingKeyFingerprint + " (signature fingerprint: " + fingerprint + ")");
}
throw new SignatureValidationException("Signature was not created by " + signingKeyFingerprint +
" (signature issuer: " + Arrays.toString(hex) + ")");
}
// No issuer information found, so we cannot rule out that we did not create the sig

View File

@ -45,6 +45,7 @@ import org.pgpainless.algorithm.SignatureSubpacket;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
import org.pgpainless.key.OpenPgpFingerprint;
import org.pgpainless.key.OpenPgpV4Fingerprint;
import org.pgpainless.key.OpenPgpV5Fingerprint;
import org.pgpainless.key.generation.type.KeyType;
import org.pgpainless.signature.SignatureUtils;
@ -72,6 +73,12 @@ public final class SignatureSubpacketsUtil {
return hashedOrUnhashed(signature, SignatureSubpacket.issuerFingerprint);
}
public static List<IssuerFingerprint> getIssuerFingerprints(PGPSignature signature) {
List<IssuerFingerprint> fingerprints = getSignatureSubpackets(signature.getHashedSubPackets(), SignatureSubpacket.issuerFingerprint);
fingerprints.addAll(getSignatureSubpackets(signature.getUnhashedSubPackets(), SignatureSubpacket.issuerFingerprint));
return fingerprints;
}
/**
* 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.
@ -85,12 +92,31 @@ public final class SignatureSubpacketsUtil {
return null;
}
OpenPgpFingerprint fingerprint = null;
if (subpacket.getKeyVersion() == 4) {
fingerprint = new OpenPgpV4Fingerprint(subpacket.getFingerprint());
return issuerFingerprintToOpenPgpFingerprint(subpacket);
}
return fingerprint;
public static List<OpenPgpFingerprint> getIssuerFingerprintsAsOpenPgpFingerprints(PGPSignature signature) {
List<IssuerFingerprint> subpackets = getIssuerFingerprints(signature);
List<OpenPgpFingerprint> fingerprints = new ArrayList<>();
for (IssuerFingerprint subpacket : subpackets) {
OpenPgpFingerprint fingerprint = issuerFingerprintToOpenPgpFingerprint(subpacket);
if (fingerprint != null) {
fingerprints.add(fingerprint);
}
}
return fingerprints;
}
private static OpenPgpFingerprint issuerFingerprintToOpenPgpFingerprint(@Nonnull IssuerFingerprint issuerFingerprint) {
if (issuerFingerprint.getKeyVersion() == 4) {
return new OpenPgpV4Fingerprint(issuerFingerprint.getFingerprint());
} else if (issuerFingerprint.getKeyVersion() == 5) {
return new OpenPgpV5Fingerprint(issuerFingerprint.getFingerprint());
} else {
return null;
}
}
public static List<IssuerKeyID> getIssuerKeyIds(PGPSignature signature) {

View File

@ -1320,25 +1320,7 @@ public class CertificateValidatorTest {
return new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8));
}
@Test
public void testNoIssuer() throws IOException, PGPException {
String SIG = "-----BEGIN PGP SIGNATURE-----\n" +
"\n" +
"wsExBAABCABlBYJhBZl3RxQAAAAAAB4AIHNhbHRAbm90YXRpb25zLnNlcXVvaWEt\n" +
"cGdwLm9yZ6u++H3Om9Z3+mn7nyt3TSxs4o/D4fHi+e+X+/7jEj3dFiEE0aZuGiOx\n" +
"gsmYD3iM+/zIKgFeczAAAOQKDACD4krXC/9GQQcl+waBglgmQaUWd+YEpXlPDQc3\n" +
"dPF7kkc/pRmmGVur8kk8rWmuQwNvQt7vGD2YJzcWBFvSSZlbYa89Gd3L5H2keTYC\n" +
"0Q/VbZqBp9ONtK3D9Yrti2jMYdcb30LZQnsSUsB0mwX41AxjPLx0FAcVMAEADjq/\n" +
"zLkJ1y+vXCko653qS/um6VQGK8JeNHRz8ghrc4k0E2yYadWWO07wxuoTAlwskf32\n" +
"MdSVeIUQIEZqEgWMbqtqglqL3eNWk56IonCePtulkQSN6qi4JpqTeudlOm+Zp/IN\n" +
"Tk+vrdS8PyQ0CiM794t/iHZDK5Jyz4ccFhTLPbF58xnwkwqdWPdf8WjCzaLfZcuN\n" +
"zajVdDXOBjNC1s10voENZhChsZYc9BfHqVMrOe8ngLKCCzqlJ/E930OJiZX5pc+H\n" +
"mHaHdn0m6lrhM5JX6RT9TNS/yrjdpkScb68Rcf+rXdeSoCE/aDOCVXhIhVTMQ9di\n" +
"Ch80fhwtYYjAM9G0/AEs7uBCGQQ=\n" +
"=NDO/\n" +
"-----END PGP SIGNATURE-----\n";
String CERT = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
private static final String CERT = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
"Comment: Bob's OpenPGP certificate\n" +
"\n" +
"mQGNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" +
@ -1380,7 +1362,154 @@ public class CertificateValidatorTest {
"NEJd3XZRzaXZE2aAMQ==\n" +
"=NXei\n" +
"-----END PGP PUBLIC KEY BLOCK-----\n";
String DATA = "Hello World :)";
private static final String DATA = "Hello World :)";
@Test
public void testNoIssuer() throws IOException, PGPException {
String SIG = "-----BEGIN PGP SIGNATURE-----\n" +
"\n" +
"wsExBAABCABlBYJhBZl3RxQAAAAAAB4AIHNhbHRAbm90YXRpb25zLnNlcXVvaWEt\n" +
"cGdwLm9yZ6u++H3Om9Z3+mn7nyt3TSxs4o/D4fHi+e+X+/7jEj3dFiEE0aZuGiOx\n" +
"gsmYD3iM+/zIKgFeczAAAOQKDACD4krXC/9GQQcl+waBglgmQaUWd+YEpXlPDQc3\n" +
"dPF7kkc/pRmmGVur8kk8rWmuQwNvQt7vGD2YJzcWBFvSSZlbYa89Gd3L5H2keTYC\n" +
"0Q/VbZqBp9ONtK3D9Yrti2jMYdcb30LZQnsSUsB0mwX41AxjPLx0FAcVMAEADjq/\n" +
"zLkJ1y+vXCko653qS/um6VQGK8JeNHRz8ghrc4k0E2yYadWWO07wxuoTAlwskf32\n" +
"MdSVeIUQIEZqEgWMbqtqglqL3eNWk56IonCePtulkQSN6qi4JpqTeudlOm+Zp/IN\n" +
"Tk+vrdS8PyQ0CiM794t/iHZDK5Jyz4ccFhTLPbF58xnwkwqdWPdf8WjCzaLfZcuN\n" +
"zajVdDXOBjNC1s10voENZhChsZYc9BfHqVMrOe8ngLKCCzqlJ/E930OJiZX5pc+H\n" +
"mHaHdn0m6lrhM5JX6RT9TNS/yrjdpkScb68Rcf+rXdeSoCE/aDOCVXhIhVTMQ9di\n" +
"Ch80fhwtYYjAM9G0/AEs7uBCGQQ=\n" +
"=NDO/\n" +
"-----END PGP SIGNATURE-----\n";
DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify()
.onInputStream(new ByteArrayInputStream(DATA.getBytes(StandardCharsets.UTF_8)))
.withOptions(new ConsumerOptions()
.addVerificationCert(PGPainless.readKeyRing().publicKeyRing(CERT))
.addVerificationOfDetachedSignatures(new ByteArrayInputStream(SIG.getBytes(StandardCharsets.UTF_8))));
Streams.drain(decryptionStream);
decryptionStream.close();
OpenPgpMetadata metadata = decryptionStream.getResult();
assertFalse(metadata.getVerifiedSignatures().isEmpty());
}
@Test
public void testIssuerFakeIssuer() throws IOException, PGPException {
String SIG = "-----BEGIN PGP SIGNATURE-----\n" +
"\n" +
"wsEuBAABCABOBYJiVdvoRxQAAAAAAB4AIHNhbHRAbm90YXRpb25zLnNlcXVvaWEt\n" +
"cGdwLm9yZziTJYEXbvrPrSZC/ZhOrQRXem5VvmP4cAAyu8+OsWYMABQJEPv8yCoB\n" +
"XnMwCRCqqru7zMzd3WftC/9IjroXgdTFm7HUrDoYoqj17IwoIRhjeUkuPfmZKfSv\n" +
"6F6YgHgTFQcMyLTcUzL30qzLDYLjWxZboiv2dvZylMWwzwXLfMM9DD2xNs/zAsgJ\n" +
"5op9wgzerx2WCzJYWg79wknkMNzILyscjBOIiSK1nXYZg8D7+Y6ZGr5qjufQ6++u\n" +
"xT6EDEK8WwjWIFyV1JO30e6o/mFXiskC4HeClblhBbcNV2tx3Uz0rY8Jstfa2Drr\n" +
"Eb4G3MoxCE8GVh3xGwbXZIeVzUX7xNI0n/9KoTjNq3CJEt+bN0AZCL/MIsns0b4B\n" +
"URMrM9KcC8CAUulqVqWp1pPvWxNR4Z5rBy5VIboFN1Lmdk261u5xhEDQd1ZYceaX\n" +
"AR46tmdim/hdh3wJ66KGofKGvk47ZZi5uGZngwWPOUfsyPkMWt4+e6HKiOHeWPlo\n" +
"wE9zbawxE4kXGZXtPyzYWJ93kYpKiYBsTBIaBEHu7TekWL90kyOxvLojBxvKzV5b\n" +
"LTBxodwh1J36MsCTyl7J8a4=\n" +
"=hGQP\n" +
"-----END PGP SIGNATURE-----\n";
DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify()
.onInputStream(new ByteArrayInputStream(DATA.getBytes(StandardCharsets.UTF_8)))
.withOptions(new ConsumerOptions()
.addVerificationCert(PGPainless.readKeyRing().publicKeyRing(CERT))
.addVerificationOfDetachedSignatures(new ByteArrayInputStream(SIG.getBytes(StandardCharsets.UTF_8))));
Streams.drain(decryptionStream);
decryptionStream.close();
OpenPgpMetadata metadata = decryptionStream.getResult();
assertFalse(metadata.getVerifiedSignatures().isEmpty());
}
@Test
public void testFakeIssuerIssuer() throws IOException, PGPException {
String SIG = "-----BEGIN PGP SIGNATURE-----\n" +
"\n" +
"wsEuBAABCABOBYJiVdvoRxQAAAAAAB4AIHNhbHRAbm90YXRpb25zLnNlcXVvaWEt\n" +
"cGdwLm9yZxmM4YWln474ON+fma6Zg9RcflZ5IxmwImPZCATNyow8ABQJEKqqu7vM\n" +
"zN3dCRD7/MgqAV5zMB4GC/9qR05itFbTxjlHti1S7V2foZgqgb5KR8LbWLyX/m3j\n" +
"YctvlPyDmnIAp9vzI1P8JwQK5ubCx5/BcDgU6JgL9hl5JmtpDLfCdBypOCdZJhrz\n" +
"+MDvrxohWXXVHhWTbBoDu3U+5e7HuoDPrvsxEFTG/BVxXlLlOnVKJeTES3BEFj+G\n" +
"U8x8chWW82Koh/3sBE2+FPHCepZAlkrr6TF+j8QVmZ3kz5Qd7Y7hXjXi3BA2Fb1g\n" +
"LNCNATzt/Uuc42MIjaDuXqvvqrnR/DlRCKXZKwurp1Gcc2kGE4iYw1vzf2kcKUS6\n" +
"0gQ2wCexgdiojUheHXNaDp6pgng9V2B+T1GOUhEUCYh8pnQRva0HkueLbu5MVXuE\n" +
"h6GbId1qkRvmDiVSjJIY6AbU1uryvNjRCAh+zTPhVSBc5ZuWpAuGeiK+os+sVLbM\n" +
"TYozxdINWMwlLbueD0rPy2iAKVF9xRy/uMIUlXBOOFa0vtr/LbEbt/Z+HEmkEbSA\n" +
"nOU6uODYKlnq6J5e1oykx1I=\n" +
"=HwGW\n" +
"-----END PGP SIGNATURE-----\n";
DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify()
.onInputStream(new ByteArrayInputStream(DATA.getBytes(StandardCharsets.UTF_8)))
.withOptions(new ConsumerOptions()
.addVerificationCert(PGPainless.readKeyRing().publicKeyRing(CERT))
.addVerificationOfDetachedSignatures(new ByteArrayInputStream(SIG.getBytes(StandardCharsets.UTF_8))));
Streams.drain(decryptionStream);
decryptionStream.close();
OpenPgpMetadata metadata = decryptionStream.getResult();
assertFalse(metadata.getVerifiedSignatures().isEmpty());
}
@Test
public void testIssuerFakeIssuerV6IssuerFp() throws IOException, PGPException {
String SIG = "-----BEGIN PGP SIGNATURE-----\n" +
"\n" +
"wsFTBAABCABOBYJiVdvoRxQAAAAAAB4AIHNhbHRAbm90YXRpb25zLnNlcXVvaWEt\n" +
"cGdwLm9yZ3l3fkL3IE31d0/2jrnPb7vgSpgZLx/yyaA/rq5a4IgFADkJEPv8yCoB\n" +
"XnMwCRCqqru7zMzd3SQhBqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\n" +
"qqq1NgwAj0/RjdIITzOC6RblmUrFiB4GxcE5704Kcs++AfEDNTkHOT7j6VGdj+IL\n" +
"xKeEHq0LTH8OIWXtAQUxZjJaHVMwwsA7D3BYWaL8YJ5dgbDrtSAXyGXz5+WBPVFQ\n" +
"ucka8ZRsolCF2VA6WuY1LI4cUYVx0Y6ZvaNtbyhEUtDQhu9UDJN8Ty18uq3M2Trb\n" +
"MwGJBpb92zrLtmIhhuoeIienSPYek+nCs1dVPBIu/Z4nyUaDoWSEWPgKJd+7dB6q\n" +
"v6kMHKpN/tu6Wq0jJ28E2IkvNH4+OdPmKFzHcDF3dXxd8hwzsXPolDA3yPO+891Q\n" +
"wnDKUvvlKbzz2Dvfce2Qv2DQkWufWrhSS8j6zqo0O97AEPpDSGoIMFlphvpcp/S5\n" +
"sz+okAOMU1lfhrMx3rZPi31HgfhcPExRmQBQOdxRXD0nqaCwZJWKRsUK1XxtQNHk\n" +
"r7al/8tPjlHs2KhE/XPYSjI3rc8ZnSvhnl3yzjSkDGkcM0bt7O+HpZUuQNvTy76K\n" +
"tXgaehUQ\n" +
"=v71i\n" +
"-----END PGP SIGNATURE-----\n";
DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify()
.onInputStream(new ByteArrayInputStream(DATA.getBytes(StandardCharsets.UTF_8)))
.withOptions(new ConsumerOptions()
.addVerificationCert(PGPainless.readKeyRing().publicKeyRing(CERT))
.addVerificationOfDetachedSignatures(new ByteArrayInputStream(SIG.getBytes(StandardCharsets.UTF_8))));
Streams.drain(decryptionStream);
decryptionStream.close();
OpenPgpMetadata metadata = decryptionStream.getResult();
assertFalse(metadata.getVerifiedSignatures().isEmpty());
}
@Test
public void testFakeIssuerIssuerV6IssuerFp() throws IOException, PGPException {
String SIG = "-----BEGIN PGP SIGNATURE-----\n" +
"\n" +
"wsEuBAABCABOBYJiVdvoRxQAAAAAAB4AIHNhbHRAbm90YXRpb25zLnNlcXVvaWEt\n" +
"cGdwLm9yZziTJYEXbvrPrSZC/ZhOrQRXem5VvmP4cAAyu8+OsWYMABQJEPv8yCoB\n" +
"XnMwCRCqqru7zMzd3WftC/9IjroXgdTFm7HUrDoYoqj17IwoIRhjeUkuPfmZKfSv\n" +
"6F6YgHgTFQcMyLTcUzL30qzLDYLjWxZboiv2dvZylMWwzwXLfMM9DD2xNs/zAsgJ\n" +
"5op9wgzerx2WCzJYWg79wknkMNzILyscjBOIiSK1nXYZg8D7+Y6ZGr5qjufQ6++u\n" +
"xT6EDEK8WwjWIFyV1JO30e6o/mFXiskC4HeClblhBbcNV2tx3Uz0rY8Jstfa2Drr\n" +
"Eb4G3MoxCE8GVh3xGwbXZIeVzUX7xNI0n/9KoTjNq3CJEt+bN0AZCL/MIsns0b4B\n" +
"URMrM9KcC8CAUulqVqWp1pPvWxNR4Z5rBy5VIboFN1Lmdk261u5xhEDQd1ZYceaX\n" +
"AR46tmdim/hdh3wJ66KGofKGvk47ZZi5uGZngwWPOUfsyPkMWt4+e6HKiOHeWPlo\n" +
"wE9zbawxE4kXGZXtPyzYWJ93kYpKiYBsTBIaBEHu7TekWL90kyOxvLojBxvKzV5b\n" +
"LTBxodwh1J36MsCTyl7J8a4=\n" +
"=hGQP\n" +
"-----END PGP SIGNATURE-----\n";
DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify()
.onInputStream(new ByteArrayInputStream(DATA.getBytes(StandardCharsets.UTF_8)))