mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-12-01 16:52:08 +01:00
Proper Signature Verification
This commit is contained in:
parent
6ee8a9416f
commit
64cc9ecca4
67 changed files with 7950 additions and 688 deletions
|
@ -96,6 +96,10 @@ allprojects {
|
||||||
|
|
||||||
test {
|
test {
|
||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
|
testLogging {
|
||||||
|
events "passed", "skipped", "failed"
|
||||||
|
exceptionFormat "full"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ public enum PublicKeyAlgorithm {
|
||||||
/**
|
/**
|
||||||
* RSA capable of encryption and signatures.
|
* RSA capable of encryption and signatures.
|
||||||
*/
|
*/
|
||||||
RSA_GENERAL (PublicKeyAlgorithmTags.RSA_GENERAL),
|
RSA_GENERAL (PublicKeyAlgorithmTags.RSA_GENERAL, true, true),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RSA with usage encryption.
|
* RSA with usage encryption.
|
||||||
|
@ -38,7 +38,7 @@ public enum PublicKeyAlgorithm {
|
||||||
* @deprecated see https://tools.ietf.org/html/rfc4880#section-13.5
|
* @deprecated see https://tools.ietf.org/html/rfc4880#section-13.5
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
RSA_ENCRYPT (PublicKeyAlgorithmTags.RSA_ENCRYPT),
|
RSA_ENCRYPT (PublicKeyAlgorithmTags.RSA_ENCRYPT, false, true),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RSA with usage of creating signatures.
|
* RSA with usage of creating signatures.
|
||||||
|
@ -46,34 +46,34 @@ public enum PublicKeyAlgorithm {
|
||||||
* @deprecated see https://tools.ietf.org/html/rfc4880#section-13.5
|
* @deprecated see https://tools.ietf.org/html/rfc4880#section-13.5
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
RSA_SIGN (PublicKeyAlgorithmTags.RSA_SIGN),
|
RSA_SIGN (PublicKeyAlgorithmTags.RSA_SIGN, true, false),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ElGamal with usage encryption.
|
* ElGamal with usage encryption.
|
||||||
*/
|
*/
|
||||||
ELGAMAL_ENCRYPT (PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT),
|
ELGAMAL_ENCRYPT (PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT, false, true),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Digital Signature Algorithm.
|
* Digital Signature Algorithm.
|
||||||
*/
|
*/
|
||||||
DSA (PublicKeyAlgorithmTags.DSA),
|
DSA (PublicKeyAlgorithmTags.DSA, true, false),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* EC is deprecated.
|
* EC is deprecated.
|
||||||
* @deprecated use {@link #ECDH} instead.
|
* @deprecated use {@link #ECDH} instead.
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
EC (PublicKeyAlgorithmTags.EC),
|
EC (PublicKeyAlgorithmTags.EC, false, true),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Elliptic Curve Diffie-Hellman.
|
* Elliptic Curve Diffie-Hellman.
|
||||||
*/
|
*/
|
||||||
ECDH (PublicKeyAlgorithmTags.ECDH),
|
ECDH (PublicKeyAlgorithmTags.ECDH, false, true),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Elliptic Curve Digital Signature Algorithm.
|
* Elliptic Curve Digital Signature Algorithm.
|
||||||
*/
|
*/
|
||||||
ECDSA (PublicKeyAlgorithmTags.ECDSA),
|
ECDSA (PublicKeyAlgorithmTags.ECDSA, true, false),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ElGamal General.
|
* ElGamal General.
|
||||||
|
@ -81,17 +81,17 @@ public enum PublicKeyAlgorithm {
|
||||||
* @deprecated see https://tools.ietf.org/html/rfc4880#section-13.8
|
* @deprecated see https://tools.ietf.org/html/rfc4880#section-13.8
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
ELGAMAL_GENERAL (PublicKeyAlgorithmTags.ELGAMAL_GENERAL),
|
ELGAMAL_GENERAL (PublicKeyAlgorithmTags.ELGAMAL_GENERAL, true, true),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Diffie-Hellman key exchange algorithm.
|
* Diffie-Hellman key exchange algorithm.
|
||||||
*/
|
*/
|
||||||
DIFFIE_HELLMAN (PublicKeyAlgorithmTags.DIFFIE_HELLMAN),
|
DIFFIE_HELLMAN (PublicKeyAlgorithmTags.DIFFIE_HELLMAN, false, true),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Digital Signature Algorithm based on twisted Edwards Curves.
|
* Digital Signature Algorithm based on twisted Edwards Curves.
|
||||||
*/
|
*/
|
||||||
EDDSA (PublicKeyAlgorithmTags.EDDSA),
|
EDDSA (PublicKeyAlgorithmTags.EDDSA, true, false),
|
||||||
;
|
;
|
||||||
|
|
||||||
private static final Map<Integer, PublicKeyAlgorithm> MAP = new ConcurrentHashMap<>();
|
private static final Map<Integer, PublicKeyAlgorithm> MAP = new ConcurrentHashMap<>();
|
||||||
|
@ -114,9 +114,13 @@ public enum PublicKeyAlgorithm {
|
||||||
}
|
}
|
||||||
|
|
||||||
private final int algorithmId;
|
private final int algorithmId;
|
||||||
|
private final boolean signingCapable;
|
||||||
|
private final boolean encryptionCapable;
|
||||||
|
|
||||||
PublicKeyAlgorithm(int algorithmId) {
|
PublicKeyAlgorithm(int algorithmId, boolean signingCapable, boolean encryptionCapable) {
|
||||||
this.algorithmId = algorithmId;
|
this.algorithmId = algorithmId;
|
||||||
|
this.signingCapable = signingCapable;
|
||||||
|
this.encryptionCapable = encryptionCapable;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -127,4 +131,22 @@ public enum PublicKeyAlgorithm {
|
||||||
public int getAlgorithmId() {
|
public int getAlgorithmId() {
|
||||||
return algorithmId;
|
return algorithmId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if this public key algorithm is able to create signatures.
|
||||||
|
*
|
||||||
|
* @return true if can sign
|
||||||
|
*/
|
||||||
|
public boolean isSigningCapable() {
|
||||||
|
return signingCapable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if this public key algorithm can be used as an encryption algorithm.
|
||||||
|
*
|
||||||
|
* @return true if can encrypt
|
||||||
|
*/
|
||||||
|
public boolean isEncryptionCapable() {
|
||||||
|
return encryptionCapable;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -202,4 +202,32 @@ public enum SignatureType {
|
||||||
return code;
|
return code;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isRevocationSignature(int signatureType) {
|
||||||
|
return isRevocationSignature(SignatureType.valueOf(signatureType));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isRevocationSignature(SignatureType signatureType) {
|
||||||
|
switch (signatureType) {
|
||||||
|
case BINARY_DOCUMENT:
|
||||||
|
case CANONICAL_TEXT_DOCUMENT:
|
||||||
|
case STANDALONE:
|
||||||
|
case GENERIC_CERTIFICATION:
|
||||||
|
case NO_CERTIFICATION:
|
||||||
|
case CASUAL_CERTIFICATION:
|
||||||
|
case POSITIVE_CERTIFICATION:
|
||||||
|
case SUBKEY_BINDING:
|
||||||
|
case PRIMARYKEY_BINDING:
|
||||||
|
case DIRECT_KEY:
|
||||||
|
case TIMESTAMP:
|
||||||
|
case THIRD_PARTY_CONFIRMATION:
|
||||||
|
return false;
|
||||||
|
case KEY_REVOCATION:
|
||||||
|
case SUBKEY_REVOCATION:
|
||||||
|
case CERTIFICATION_REVOCATION:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unknown type: " + signatureType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,17 +18,20 @@ package org.pgpainless.decryption_verification;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
import org.bouncycastle.bcpg.MarkerPacket;
|
||||||
import org.bouncycastle.openpgp.PGPCompressedData;
|
import org.bouncycastle.openpgp.PGPCompressedData;
|
||||||
import org.bouncycastle.openpgp.PGPException;
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
import org.bouncycastle.openpgp.PGPObjectFactory;
|
import org.bouncycastle.openpgp.PGPObjectFactory;
|
||||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||||
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||||
|
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
||||||
import org.bouncycastle.openpgp.PGPSignature;
|
import org.bouncycastle.openpgp.PGPSignature;
|
||||||
import org.bouncycastle.openpgp.PGPSignatureList;
|
import org.bouncycastle.openpgp.PGPSignatureList;
|
||||||
|
@ -67,6 +70,13 @@ public class DecryptionBuilder implements DecryptionBuilderInterface {
|
||||||
return new VerifyImpl();
|
return new VerifyImpl();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Verify decryptWith(@Nonnull SecretKeyRingProtector decryptor, @Nonnull PGPSecretKeyRing secretKeyRing) throws PGPException, IOException {
|
||||||
|
DecryptionBuilder.this.decryptionKeys = new PGPSecretKeyRingCollection(Collections.singleton(secretKeyRing));
|
||||||
|
DecryptionBuilder.this.decryptionKeyDecryptor = decryptor;
|
||||||
|
return new VerifyImpl();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Verify decryptWith(@Nonnull Passphrase passphrase) {
|
public Verify decryptWith(@Nonnull Passphrase passphrase) {
|
||||||
if (passphrase.isEmpty()) {
|
if (passphrase.isEmpty()) {
|
||||||
|
@ -94,6 +104,10 @@ public class DecryptionBuilder implements DecryptionBuilderInterface {
|
||||||
pgpIn, keyFingerPrintCalculator);
|
pgpIn, keyFingerPrintCalculator);
|
||||||
Object nextObject = objectFactory.nextObject();
|
Object nextObject = objectFactory.nextObject();
|
||||||
while (nextObject != null) {
|
while (nextObject != null) {
|
||||||
|
if (nextObject instanceof MarkerPacket) {
|
||||||
|
nextObject = objectFactory.nextObject();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (nextObject instanceof PGPCompressedData) {
|
if (nextObject instanceof PGPCompressedData) {
|
||||||
PGPCompressedData compressedData = (PGPCompressedData) nextObject;
|
PGPCompressedData compressedData = (PGPCompressedData) nextObject;
|
||||||
objectFactory = new PGPObjectFactory(compressedData.getDataStream(), keyFingerPrintCalculator);
|
objectFactory = new PGPObjectFactory(compressedData.getDataStream(), keyFingerPrintCalculator);
|
||||||
|
@ -205,8 +219,8 @@ public class DecryptionBuilder implements DecryptionBuilderInterface {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DecryptionStream build() throws IOException, PGPException {
|
public DecryptionStream build() throws IOException, PGPException {
|
||||||
return DecryptionStreamFactory.create(inputStream,
|
return DecryptionStreamFactory.create(inputStream, decryptionKeys, decryptionKeyDecryptor,
|
||||||
decryptionKeys, decryptionKeyDecryptor, decryptionPassphrase, detachedSignatures, verificationKeys, missingPublicKeyCallback);
|
decryptionPassphrase, detachedSignatures, verificationKeys, missingPublicKeyCallback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ import java.util.Set;
|
||||||
import org.bouncycastle.openpgp.PGPException;
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||||
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||||
|
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
||||||
import org.bouncycastle.openpgp.PGPSignature;
|
import org.bouncycastle.openpgp.PGPSignature;
|
||||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||||
|
@ -68,6 +69,16 @@ public interface DecryptionBuilderInterface {
|
||||||
*/
|
*/
|
||||||
Verify decryptWith(@Nonnull SecretKeyRingProtector decryptor, @Nonnull PGPSecretKeyRingCollection secretKeyRings);
|
Verify decryptWith(@Nonnull SecretKeyRingProtector decryptor, @Nonnull PGPSecretKeyRingCollection secretKeyRings);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypt the encrypted data using the provided {@link PGPSecretKeyRing}.
|
||||||
|
* The secret key is unlocked by the provided {@link SecretKeyRingProtector}.
|
||||||
|
*
|
||||||
|
* @param decryptor for unlocking locked secret key
|
||||||
|
* @param secretKeyRing secret key
|
||||||
|
* @return api handle
|
||||||
|
*/
|
||||||
|
Verify decryptWith(@Nonnull SecretKeyRingProtector decryptor, @Nonnull PGPSecretKeyRing secretKeyRing) throws PGPException, IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrypt the encrypted data using a passphrase.
|
* Decrypt the encrypted data using a passphrase.
|
||||||
* Note: The passphrase MUST NOT be empty.
|
* Note: The passphrase MUST NOT be empty.
|
||||||
|
|
|
@ -15,14 +15,18 @@
|
||||||
*/
|
*/
|
||||||
package org.pgpainless.decryption_verification;
|
package org.pgpainless.decryption_verification;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
import org.bouncycastle.openpgp.PGPException;
|
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||||
|
import org.pgpainless.PGPainless;
|
||||||
|
import org.pgpainless.signature.DetachedSignature;
|
||||||
|
import org.pgpainless.signature.SignatureChainValidator;
|
||||||
|
import org.pgpainless.signature.SignatureValidationException;
|
||||||
import org.pgpainless.util.IntegrityProtectedInputStream;
|
import org.pgpainless.util.IntegrityProtectedInputStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -73,9 +77,10 @@ public class DecryptionStream extends InputStream {
|
||||||
private void maybeVerifyDetachedSignatures() {
|
private void maybeVerifyDetachedSignatures() {
|
||||||
for (DetachedSignature s : resultBuilder.getDetachedSignatures()) {
|
for (DetachedSignature s : resultBuilder.getDetachedSignatures()) {
|
||||||
try {
|
try {
|
||||||
s.setVerified(s.getSignature().verify());
|
boolean verified = SignatureChainValidator.validateSignature(s.getSignature(), (PGPPublicKeyRing) s.getSigningKeyRing(), PGPainless.getPolicy());
|
||||||
} catch (PGPException e) {
|
s.setVerified(verified);
|
||||||
LOGGER.log(Level.WARNING, "Could not verify signature of key " + s.getFingerprint(), e);
|
} catch (SignatureValidationException e) {
|
||||||
|
LOGGER.log(Level.WARNING, "Could not verify signature of key " + s.getSigningKeyIdentifier(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,7 +58,10 @@ import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||||
import org.pgpainless.exception.MessageNotIntegrityProtectedException;
|
import org.pgpainless.exception.MessageNotIntegrityProtectedException;
|
||||||
import org.pgpainless.implementation.ImplementationFactory;
|
import org.pgpainless.implementation.ImplementationFactory;
|
||||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||||
|
import org.pgpainless.key.SubkeyIdentifier;
|
||||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||||
|
import org.pgpainless.signature.DetachedSignature;
|
||||||
|
import org.pgpainless.signature.OnePassSignature;
|
||||||
import org.pgpainless.util.IntegrityProtectedInputStream;
|
import org.pgpainless.util.IntegrityProtectedInputStream;
|
||||||
import org.pgpainless.util.Passphrase;
|
import org.pgpainless.util.Passphrase;
|
||||||
|
|
||||||
|
@ -107,13 +110,14 @@ public final class DecryptionStreamFactory {
|
||||||
if (detachedSignatures != null) {
|
if (detachedSignatures != null) {
|
||||||
pgpInputStream = inputStream;
|
pgpInputStream = inputStream;
|
||||||
for (PGPSignature signature : detachedSignatures) {
|
for (PGPSignature signature : detachedSignatures) {
|
||||||
PGPPublicKey signingKey = factory.findSignatureVerificationKey(signature.getKeyID());
|
PGPPublicKeyRing signingKeyRing = factory.findSignatureVerificationKeyRing(signature.getKeyID());
|
||||||
if (signingKey == null) {
|
if (signingKeyRing == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
PGPPublicKey signingKey = signingKeyRing.getPublicKey(signature.getKeyID());
|
||||||
signature.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), signingKey);
|
signature.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), signingKey);
|
||||||
factory.resultBuilder.addDetachedSignature(
|
factory.resultBuilder.addDetachedSignature(
|
||||||
new DetachedSignature(signature, new OpenPgpV4Fingerprint(signingKey)));
|
new DetachedSignature(signature, signingKeyRing, new SubkeyIdentifier(signingKeyRing, signature.getKeyID())));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
PGPObjectFactory objectFactory = new PGPObjectFactory(
|
PGPObjectFactory objectFactory = new PGPObjectFactory(
|
||||||
|
@ -312,59 +316,31 @@ public final class DecryptionStreamFactory {
|
||||||
LOGGER.log(LEVEL, "Message contains OnePassSignature from " + Long.toHexString(keyId));
|
LOGGER.log(LEVEL, "Message contains OnePassSignature from " + Long.toHexString(keyId));
|
||||||
|
|
||||||
// Find public key
|
// Find public key
|
||||||
PGPPublicKey verificationKey = findSignatureVerificationKey(keyId);
|
PGPPublicKeyRing verificationKeyRing = findSignatureVerificationKeyRing(keyId);
|
||||||
if (verificationKey == null) {
|
if (verificationKeyRing == null) {
|
||||||
LOGGER.log(LEVEL, "Missing verification key from " + Long.toHexString(keyId));
|
LOGGER.log(LEVEL, "Missing verification key from " + Long.toHexString(keyId));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
PGPPublicKey verificationKey = verificationKeyRing.getPublicKey(keyId);
|
||||||
|
|
||||||
signature.init(verifierBuilderProvider, verificationKey);
|
signature.init(verifierBuilderProvider, verificationKey);
|
||||||
OpenPgpV4Fingerprint fingerprint = new OpenPgpV4Fingerprint(verificationKey);
|
OpenPgpV4Fingerprint fingerprint = new OpenPgpV4Fingerprint(verificationKey);
|
||||||
OnePassSignature onePassSignature = new OnePassSignature(signature, fingerprint);
|
OnePassSignature onePassSignature = new OnePassSignature(signature, verificationKeyRing);
|
||||||
resultBuilder.addOnePassSignature(onePassSignature);
|
resultBuilder.addOnePassSignature(onePassSignature);
|
||||||
verifiableOnePassSignatures.put(fingerprint, onePassSignature);
|
verifiableOnePassSignatures.put(fingerprint, onePassSignature);
|
||||||
}
|
}
|
||||||
|
|
||||||
private PGPPublicKey findSignatureVerificationKey(long keyId) {
|
private PGPPublicKeyRing findSignatureVerificationKeyRing(long keyId) {
|
||||||
PGPPublicKey verificationKey = null;
|
PGPPublicKeyRing verificationKeyRing = null;
|
||||||
for (PGPPublicKeyRing publicKeyRing : verificationKeys) {
|
for (PGPPublicKeyRing publicKeyRing : verificationKeys) {
|
||||||
verificationKey = publicKeyRing.getPublicKey(keyId);
|
PGPPublicKey verificationKey = publicKeyRing.getPublicKey(keyId);
|
||||||
if (verificationKey != null) {
|
if (verificationKey != null) {
|
||||||
LOGGER.log(LEVEL, "Found public key " + Long.toHexString(keyId) + " for signature verification");
|
LOGGER.log(LEVEL, "Found public key " + Long.toHexString(keyId) + " for signature verification");
|
||||||
|
verificationKeyRing = publicKeyRing;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (verificationKey == null) {
|
return verificationKeyRing;
|
||||||
verificationKey = handleMissingVerificationKey(keyId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return verificationKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
private PGPPublicKey handleMissingVerificationKey(long keyId) {
|
|
||||||
LOGGER.log(Level.FINER, "No public key found for signature of " + Long.toHexString(keyId));
|
|
||||||
|
|
||||||
if (missingPublicKeyCallback == null) {
|
|
||||||
LOGGER.log(Level.FINER, "No MissingPublicKeyCallback registered. " +
|
|
||||||
"Skip signature of " + Long.toHexString(keyId));
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
PGPPublicKey missingPublicKey = missingPublicKeyCallback.onMissingPublicKeyEncountered(keyId);
|
|
||||||
if (missingPublicKey == null) {
|
|
||||||
LOGGER.log(Level.FINER, "MissingPublicKeyCallback did not provider key. " +
|
|
||||||
"Skip signature of " + Long.toHexString(keyId));
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (missingPublicKey.getKeyID() != keyId) {
|
|
||||||
throw new IllegalArgumentException("KeyID of the provided public key differs from the signatures keyId. " +
|
|
||||||
"The signature was created from " + Long.toHexString(keyId) + " while the provided key has ID " +
|
|
||||||
Long.toHexString(missingPublicKey.getKeyID()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return missingPublicKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,8 @@ import org.pgpainless.algorithm.CompressionAlgorithm;
|
||||||
import org.pgpainless.algorithm.StreamEncoding;
|
import org.pgpainless.algorithm.StreamEncoding;
|
||||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||||
|
import org.pgpainless.signature.DetachedSignature;
|
||||||
|
import org.pgpainless.signature.OnePassSignature;
|
||||||
|
|
||||||
public class OpenPgpMetadata {
|
public class OpenPgpMetadata {
|
||||||
|
|
||||||
|
|
|
@ -1,101 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2021 Paul Schaub.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.pgpainless.decryption_verification;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
import org.bouncycastle.bcpg.sig.NotationData;
|
|
||||||
import org.bouncycastle.openpgp.PGPDataValidationException;
|
|
||||||
import org.bouncycastle.openpgp.PGPException;
|
|
||||||
import org.bouncycastle.openpgp.PGPSignature;
|
|
||||||
import org.bouncycastle.openpgp.PGPSignatureSubpacketVector;
|
|
||||||
import org.pgpainless.algorithm.SignatureSubpacket;
|
|
||||||
import org.pgpainless.util.NotationRegistry;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility class that implements validation of signatures.
|
|
||||||
*/
|
|
||||||
public class SignatureValidationUtil {
|
|
||||||
|
|
||||||
public static void validate(PGPSignature signature) throws PGPException {
|
|
||||||
validateHashedAreaHasSignatureCreationTime(signature);
|
|
||||||
validateSignatureCreationTimeIsNotInUnhashedArea(signature);
|
|
||||||
validateSignatureDoesNotContainCriticalUnknownSubpackets(signature);
|
|
||||||
validateSignatureDoesNotContainCriticalUnknownNotations(signature);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void validateHashedAreaHasSignatureCreationTime(PGPSignature signature) throws PGPDataValidationException {
|
|
||||||
PGPSignatureSubpacketVector hashedSubpackets = signature.getHashedSubPackets();
|
|
||||||
if (hashedSubpackets.getSignatureCreationTime() == null) {
|
|
||||||
throw new PGPDataValidationException("Hashed area of the signature MUST carry signature creation time subpacket.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void validateSignatureCreationTimeIsNotInUnhashedArea(PGPSignature signature) throws PGPDataValidationException {
|
|
||||||
PGPSignatureSubpacketVector unhashedSubpackets = signature.getUnhashedSubPackets();
|
|
||||||
Date unhashedCreationTime = unhashedSubpackets.getSignatureCreationTime();
|
|
||||||
if (unhashedCreationTime == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
throw new PGPDataValidationException("Signature creation time MUST be in hashed area of the signature.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void validateSignatureDoesNotContainCriticalUnknownSubpackets(PGPSignature signature) throws PGPDataValidationException {
|
|
||||||
try {
|
|
||||||
throwIfContainsCriticalUnknownSubpacket(signature.getHashedSubPackets());
|
|
||||||
} catch (PGPDataValidationException e) {
|
|
||||||
throw new PGPDataValidationException("Signature has unknown critical subpacket in hashed area.\n" + e.getMessage());
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
throwIfContainsCriticalUnknownSubpacket(signature.getHashedSubPackets());
|
|
||||||
} catch (PGPDataValidationException e) {
|
|
||||||
throw new PGPDataValidationException("Signature has unknown critical subpacket in unhashed area.\n" + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void throwIfContainsCriticalUnknownSubpacket(PGPSignatureSubpacketVector subpacketVector) throws PGPDataValidationException {
|
|
||||||
for (int critical : subpacketVector.getCriticalTags()) {
|
|
||||||
try {
|
|
||||||
SignatureSubpacket.fromCode(critical);
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
throw new PGPDataValidationException("Unknown critical signature subpacket: " + Long.toHexString(critical));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void validateSignatureDoesNotContainCriticalUnknownNotations(PGPSignature signature) throws PGPDataValidationException {
|
|
||||||
PGPSignatureSubpacketVector hashedSubpackets = signature.getHashedSubPackets();
|
|
||||||
try {
|
|
||||||
throwIfSubpacketsContainCriticalUnknownNotation(hashedSubpackets);
|
|
||||||
} catch (PGPDataValidationException e) {
|
|
||||||
throw new PGPDataValidationException("Signature contains unknown critical notation in hashed area:\n" + e.getMessage());
|
|
||||||
}
|
|
||||||
PGPSignatureSubpacketVector unhashedSubpackets = signature.getUnhashedSubPackets();
|
|
||||||
try {
|
|
||||||
throwIfSubpacketsContainCriticalUnknownNotation(unhashedSubpackets);
|
|
||||||
} catch (PGPDataValidationException e) {
|
|
||||||
throw new PGPDataValidationException("Signature contains unknown critical notation in unhashed area:\n" + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void throwIfSubpacketsContainCriticalUnknownNotation(PGPSignatureSubpacketVector subpacketVector) throws PGPDataValidationException {
|
|
||||||
for (NotationData notation : subpacketVector.getNotationDataOccurrences()) {
|
|
||||||
if (notation.isCritical() && !NotationRegistry.getInstance().isKnownNotation(notation.getNotationName())) {
|
|
||||||
throw new PGPDataValidationException("Critical unknown notation encountered: " + notation.getNotationName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -15,20 +15,30 @@
|
||||||
*/
|
*/
|
||||||
package org.pgpainless.decryption_verification;
|
package org.pgpainless.decryption_verification;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import static org.pgpainless.signature.SignatureValidator.signatureIsEffective;
|
||||||
|
import static org.pgpainless.signature.SignatureValidator.signatureStructureIsAcceptable;
|
||||||
|
|
||||||
import java.io.FilterInputStream;
|
import java.io.FilterInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.security.SignatureException;
|
import java.security.SignatureException;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
import org.bouncycastle.openpgp.PGPException;
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
import org.bouncycastle.openpgp.PGPObjectFactory;
|
import org.bouncycastle.openpgp.PGPObjectFactory;
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||||
import org.bouncycastle.openpgp.PGPSignature;
|
import org.bouncycastle.openpgp.PGPSignature;
|
||||||
import org.bouncycastle.openpgp.PGPSignatureList;
|
import org.bouncycastle.openpgp.PGPSignatureList;
|
||||||
|
import org.pgpainless.PGPainless;
|
||||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||||
|
import org.pgpainless.policy.Policy;
|
||||||
|
import org.pgpainless.signature.OnePassSignature;
|
||||||
|
import org.pgpainless.signature.SignatureChainValidator;
|
||||||
|
import org.pgpainless.signature.SignatureValidationException;
|
||||||
|
|
||||||
public class SignatureVerifyingInputStream extends FilterInputStream {
|
public class SignatureVerifyingInputStream extends FilterInputStream {
|
||||||
|
|
||||||
|
@ -93,20 +103,30 @@ public class SignatureVerifyingInputStream extends FilterInputStream {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
verifySignatureOrThrowSignatureException(signature, fingerprint, onePassSignature);
|
verifySignatureOrThrowSignatureException(signature, onePassSignature);
|
||||||
}
|
}
|
||||||
} catch (PGPException | SignatureException e) {
|
} catch (PGPException | SignatureException e) {
|
||||||
throw new IOException(e.getMessage(), e);
|
throw new IOException(e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void verifySignatureOrThrowSignatureException(PGPSignature signature, OpenPgpV4Fingerprint fingerprint,
|
private void verifySignatureOrThrowSignatureException(PGPSignature signature, OnePassSignature onePassSignature)
|
||||||
OnePassSignature onePassSignature)
|
|
||||||
throws PGPException, SignatureException {
|
throws PGPException, SignatureException {
|
||||||
if (onePassSignature.verify(signature)) {
|
Policy policy = PGPainless.getPolicy();
|
||||||
LOGGER.log(LEVEL, "Verified signature of key " + Long.toHexString(signature.getKeyID()));
|
try {
|
||||||
} else {
|
PGPPublicKey signingKey = onePassSignature.getVerificationKeys().getPublicKey(signature.getKeyID());
|
||||||
|
signatureStructureIsAcceptable(signingKey, policy).verify(signature);
|
||||||
|
signatureIsEffective(new Date()).verify(signature);
|
||||||
|
|
||||||
|
SignatureChainValidator.validateSigningKey(signature, onePassSignature.getVerificationKeys(), PGPainless.getPolicy(), signature.getCreationTime());
|
||||||
|
|
||||||
|
} catch (SignatureValidationException e) {
|
||||||
|
throw new SignatureException("Signature key is not valid.", e);
|
||||||
|
}
|
||||||
|
if (!onePassSignature.verify(signature)) {
|
||||||
throw new SignatureException("Bad Signature of key " + signature.getKeyID());
|
throw new SignatureException("Bad Signature of key " + signature.getKeyID());
|
||||||
|
} else {
|
||||||
|
LOGGER.log(LEVEL, "Verified signature of key " + Long.toHexString(signature.getKeyID()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,37 +33,36 @@ import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||||
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
import org.bouncycastle.openpgp.PGPSecretKey;
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
|
||||||
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
|
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
|
||||||
|
import org.pgpainless.PGPainless;
|
||||||
import org.pgpainless.algorithm.CompressionAlgorithm;
|
import org.pgpainless.algorithm.CompressionAlgorithm;
|
||||||
import org.pgpainless.algorithm.HashAlgorithm;
|
import org.pgpainless.algorithm.HashAlgorithm;
|
||||||
import org.pgpainless.algorithm.KeyFlag;
|
import org.pgpainless.algorithm.KeyFlag;
|
||||||
import org.pgpainless.algorithm.SignatureType;
|
import org.pgpainless.algorithm.SignatureType;
|
||||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||||
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
||||||
import org.pgpainless.exception.SecretKeyNotFoundException;
|
import org.pgpainless.key.KeyRingValidator;
|
||||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||||
|
import org.pgpainless.key.SubkeyIdentifier;
|
||||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||||
|
import org.pgpainless.util.Passphrase;
|
||||||
|
import org.pgpainless.util.Tuple;
|
||||||
import org.pgpainless.util.selection.key.PublicKeySelectionStrategy;
|
import org.pgpainless.util.selection.key.PublicKeySelectionStrategy;
|
||||||
import org.pgpainless.util.selection.key.SecretKeySelectionStrategy;
|
import org.pgpainless.util.selection.key.SecretKeySelectionStrategy;
|
||||||
|
import org.pgpainless.util.selection.key.impl.And;
|
||||||
import org.pgpainless.util.selection.key.impl.EncryptionKeySelectionStrategy;
|
import org.pgpainless.util.selection.key.impl.EncryptionKeySelectionStrategy;
|
||||||
import org.pgpainless.util.selection.key.impl.NoRevocation;
|
import org.pgpainless.util.selection.key.impl.NoRevocation;
|
||||||
import org.pgpainless.util.selection.key.impl.SignatureKeySelectionStrategy;
|
import org.pgpainless.util.selection.key.impl.SignatureKeySelectionStrategy;
|
||||||
import org.pgpainless.util.selection.key.impl.And;
|
|
||||||
import org.pgpainless.util.selection.keyring.PublicKeyRingSelectionStrategy;
|
|
||||||
import org.pgpainless.util.selection.keyring.SecretKeyRingSelectionStrategy;
|
|
||||||
import org.pgpainless.util.MultiMap;
|
|
||||||
import org.pgpainless.util.Passphrase;
|
|
||||||
|
|
||||||
public class EncryptionBuilder implements EncryptionBuilderInterface {
|
public class EncryptionBuilder implements EncryptionBuilderInterface {
|
||||||
|
|
||||||
private final EncryptionStream.Purpose purpose;
|
private final EncryptionStream.Purpose purpose;
|
||||||
private OutputStream outputStream;
|
private OutputStream outputStream;
|
||||||
private final Set<PGPPublicKey> encryptionKeys = new HashSet<>();
|
private final Map<SubkeyIdentifier, PGPPublicKeyRing> encryptionKeys = new ConcurrentHashMap<>();
|
||||||
private final Set<Passphrase> encryptionPassphrases = new HashSet<>();
|
private final Set<Passphrase> encryptionPassphrases = new HashSet<>();
|
||||||
private boolean detachedSignature = false;
|
private boolean detachedSignature = false;
|
||||||
private SignatureType signatureType = SignatureType.BINARY_DOCUMENT;
|
private SignatureType signatureType = SignatureType.BINARY_DOCUMENT;
|
||||||
private final Set<PGPSecretKey> signingKeys = new HashSet<>();
|
private final Map<SubkeyIdentifier, PGPSecretKeyRing> signingKeys = new ConcurrentHashMap<>();
|
||||||
private SecretKeyRingProtector signingKeysDecryptor;
|
private SecretKeyRingProtector signingKeysDecryptor;
|
||||||
private SymmetricKeyAlgorithm symmetricKeyAlgorithm = SymmetricKeyAlgorithm.AES_128;
|
private SymmetricKeyAlgorithm symmetricKeyAlgorithm = SymmetricKeyAlgorithm.AES_128;
|
||||||
private HashAlgorithm hashAlgorithm = HashAlgorithm.SHA256;
|
private HashAlgorithm hashAlgorithm = HashAlgorithm.SHA256;
|
||||||
|
@ -88,58 +87,46 @@ public class EncryptionBuilder implements EncryptionBuilderInterface {
|
||||||
|
|
||||||
class ToRecipientsImpl implements ToRecipients {
|
class ToRecipientsImpl implements ToRecipients {
|
||||||
|
|
||||||
@Override
|
|
||||||
public WithAlgorithms toRecipients(@Nonnull PGPPublicKey... keys) {
|
|
||||||
if (keys.length != 0) {
|
|
||||||
List<PGPPublicKey> encryptionKeys = new ArrayList<>();
|
|
||||||
for (PGPPublicKey k : keys) {
|
|
||||||
if (encryptionKeySelector().accept(k)) {
|
|
||||||
encryptionKeys.add(k);
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("Key " + k.getKeyID() + " is not a valid encryption key.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (encryptionKeys.isEmpty()) {
|
|
||||||
throw new IllegalArgumentException("No valid encryption keys found!");
|
|
||||||
}
|
|
||||||
EncryptionBuilder.this.encryptionKeys.addAll(encryptionKeys);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new WithAlgorithmsImpl();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public WithAlgorithms toRecipients(@Nonnull PGPPublicKeyRing... keys) {
|
public WithAlgorithms toRecipients(@Nonnull PGPPublicKeyRing... keys) {
|
||||||
if (keys.length != 0) {
|
if (keys.length == 0) {
|
||||||
List<PGPPublicKey> encryptionKeys = new ArrayList<>();
|
throw new IllegalArgumentException("No public keys provided.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<SubkeyIdentifier, PGPPublicKeyRing> encryptionKeys = new ConcurrentHashMap<>();
|
||||||
for (PGPPublicKeyRing ring : keys) {
|
for (PGPPublicKeyRing ring : keys) {
|
||||||
for (PGPPublicKey k : ring) {
|
PGPPublicKeyRing validatedKeyRing = KeyRingValidator.validate(ring, PGPainless.getPolicy());
|
||||||
|
for (PGPPublicKey k : validatedKeyRing) {
|
||||||
if (encryptionKeySelector().accept(k)) {
|
if (encryptionKeySelector().accept(k)) {
|
||||||
encryptionKeys.add(k);
|
encryptionKeys.put(new SubkeyIdentifier(ring, k.getKeyID()), ring);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (encryptionKeys.isEmpty()) {
|
if (encryptionKeys.isEmpty()) {
|
||||||
throw new IllegalArgumentException("No valid encryption keys found!");
|
throw new IllegalArgumentException("No valid encryption keys found!");
|
||||||
}
|
}
|
||||||
EncryptionBuilder.this.encryptionKeys.addAll(encryptionKeys);
|
EncryptionBuilder.this.encryptionKeys.putAll(encryptionKeys);
|
||||||
}
|
|
||||||
|
|
||||||
return new WithAlgorithmsImpl();
|
return new WithAlgorithmsImpl();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getPrimaryUserId(PGPPublicKey publicKey) {
|
||||||
|
// TODO: Use real function to get primary userId.
|
||||||
|
return publicKey.getUserIDs().next();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public WithAlgorithms toRecipients(@Nonnull PGPPublicKeyRingCollection... keys) {
|
public WithAlgorithms toRecipients(@Nonnull PGPPublicKeyRingCollection... keys) {
|
||||||
if (keys.length != 0) {
|
if (keys.length == 0) {
|
||||||
List<PGPPublicKey> encryptionKeys = new ArrayList<>();
|
throw new IllegalArgumentException("No key ring collections provided.");
|
||||||
|
}
|
||||||
|
|
||||||
for (PGPPublicKeyRingCollection collection : keys) {
|
for (PGPPublicKeyRingCollection collection : keys) {
|
||||||
for (PGPPublicKeyRing ring : collection) {
|
for (PGPPublicKeyRing ring : collection) {
|
||||||
|
Map<SubkeyIdentifier, PGPPublicKeyRing> encryptionKeys = new ConcurrentHashMap<>();
|
||||||
for (PGPPublicKey k : ring) {
|
for (PGPPublicKey k : ring) {
|
||||||
if (encryptionKeySelector().accept(k)) {
|
if (encryptionKeySelector().accept(k)) {
|
||||||
encryptionKeys.add(k);
|
encryptionKeys.put(new SubkeyIdentifier(ring, k.getKeyID()), ring);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,32 +134,8 @@ public class EncryptionBuilder implements EncryptionBuilderInterface {
|
||||||
throw new IllegalArgumentException("No valid encryption keys found!");
|
throw new IllegalArgumentException("No valid encryption keys found!");
|
||||||
}
|
}
|
||||||
|
|
||||||
EncryptionBuilder.this.encryptionKeys.addAll(encryptionKeys);
|
EncryptionBuilder.this.encryptionKeys.putAll(encryptionKeys);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new WithAlgorithmsImpl();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <O> WithAlgorithms toRecipients(@Nonnull PublicKeyRingSelectionStrategy<O> ringSelectionStrategy,
|
|
||||||
@Nonnull MultiMap<O, PGPPublicKeyRingCollection> keys) {
|
|
||||||
if (keys.isEmpty()) {
|
|
||||||
throw new IllegalArgumentException("Recipient map MUST NOT be empty.");
|
|
||||||
}
|
|
||||||
MultiMap<O, PGPPublicKeyRing> acceptedKeyRings = ringSelectionStrategy.selectKeyRingsFromCollections(keys);
|
|
||||||
for (O identifier : acceptedKeyRings.keySet()) {
|
|
||||||
Set<PGPPublicKeyRing> acceptedSet = acceptedKeyRings.get(identifier);
|
|
||||||
for (PGPPublicKeyRing ring : acceptedSet) {
|
|
||||||
for (PGPPublicKey k : ring) {
|
|
||||||
if (encryptionKeySelector().accept(k)) {
|
|
||||||
EncryptionBuilder.this.encryptionKeys.add(k);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (EncryptionBuilder.this.encryptionKeys.isEmpty()) {
|
|
||||||
throw new IllegalArgumentException("No valid encryption keys found!");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new WithAlgorithmsImpl();
|
return new WithAlgorithmsImpl();
|
||||||
|
@ -199,33 +162,23 @@ public class EncryptionBuilder implements EncryptionBuilderInterface {
|
||||||
|
|
||||||
class WithAlgorithmsImpl implements WithAlgorithms {
|
class WithAlgorithmsImpl implements WithAlgorithms {
|
||||||
|
|
||||||
@Override
|
|
||||||
public WithAlgorithms andToSelf(@Nonnull PGPPublicKey... keys) {
|
|
||||||
if (keys.length == 0) {
|
|
||||||
throw new IllegalArgumentException("Recipient list MUST NOT be empty.");
|
|
||||||
}
|
|
||||||
for (PGPPublicKey k : keys) {
|
|
||||||
if (encryptionKeySelector().accept(k)) {
|
|
||||||
EncryptionBuilder.this.encryptionKeys.add(k);
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("Key " + k.getKeyID() + " is not a valid encryption key.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public WithAlgorithms andToSelf(@Nonnull PGPPublicKeyRing... keys) {
|
public WithAlgorithms andToSelf(@Nonnull PGPPublicKeyRing... keys) {
|
||||||
if (keys.length == 0) {
|
if (keys.length == 0) {
|
||||||
throw new IllegalArgumentException("Recipient list MUST NOT be empty.");
|
throw new IllegalArgumentException("Recipient list MUST NOT be empty.");
|
||||||
}
|
}
|
||||||
for (PGPPublicKeyRing ring : keys) {
|
for (PGPPublicKeyRing ring : keys) {
|
||||||
|
Map<SubkeyIdentifier, PGPPublicKeyRing> encryptionKeys = new ConcurrentHashMap<>();
|
||||||
for (Iterator<PGPPublicKey> i = ring.getPublicKeys(); i.hasNext(); ) {
|
for (Iterator<PGPPublicKey> i = ring.getPublicKeys(); i.hasNext(); ) {
|
||||||
PGPPublicKey key = i.next();
|
PGPPublicKey key = i.next();
|
||||||
if (encryptionKeySelector().accept(key)) {
|
if (encryptionKeySelector().accept(key)) {
|
||||||
EncryptionBuilder.this.encryptionKeys.add(key);
|
encryptionKeys.put(new SubkeyIdentifier(ring, key.getKeyID()), ring);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (encryptionKeys.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("No suitable encryption key found in key ring " + new OpenPgpV4Fingerprint(ring));
|
||||||
|
}
|
||||||
|
EncryptionBuilder.this.encryptionKeys.putAll(encryptionKeys);
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -233,34 +186,17 @@ public class EncryptionBuilder implements EncryptionBuilderInterface {
|
||||||
@Override
|
@Override
|
||||||
public WithAlgorithms andToSelf(@Nonnull PGPPublicKeyRingCollection keys) {
|
public WithAlgorithms andToSelf(@Nonnull PGPPublicKeyRingCollection keys) {
|
||||||
for (PGPPublicKeyRing ring : keys) {
|
for (PGPPublicKeyRing ring : keys) {
|
||||||
|
Map<SubkeyIdentifier, PGPPublicKeyRing> encryptionKeys = new ConcurrentHashMap<>();
|
||||||
for (Iterator<PGPPublicKey> i = ring.getPublicKeys(); i.hasNext(); ) {
|
for (Iterator<PGPPublicKey> i = ring.getPublicKeys(); i.hasNext(); ) {
|
||||||
PGPPublicKey key = i.next();
|
PGPPublicKey key = i.next();
|
||||||
if (encryptionKeySelector().accept(key)) {
|
if (encryptionKeySelector().accept(key)) {
|
||||||
EncryptionBuilder.this.encryptionKeys.add(key);
|
encryptionKeys.put(new SubkeyIdentifier(ring, key.getKeyID()), ring);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <O> WithAlgorithms andToSelf(@Nonnull PublicKeyRingSelectionStrategy<O> ringSelectionStrategy,
|
|
||||||
@Nonnull MultiMap<O, PGPPublicKeyRingCollection> keys) {
|
|
||||||
if (keys.isEmpty()) {
|
|
||||||
throw new IllegalArgumentException("Recipient list MUST NOT be empty.");
|
|
||||||
}
|
|
||||||
MultiMap<O, PGPPublicKeyRing> acceptedKeyRings =
|
|
||||||
ringSelectionStrategy.selectKeyRingsFromCollections(keys);
|
|
||||||
for (O identifier : acceptedKeyRings.keySet()) {
|
|
||||||
Set<PGPPublicKeyRing> acceptedSet = acceptedKeyRings.get(identifier);
|
|
||||||
for (PGPPublicKeyRing k : acceptedSet) {
|
|
||||||
for (Iterator<PGPPublicKey> i = k.getPublicKeys(); i.hasNext(); ) {
|
|
||||||
PGPPublicKey key = i.next();
|
|
||||||
if (encryptionKeySelector().accept(key)) {
|
|
||||||
EncryptionBuilder.this.encryptionKeys.add(key);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (encryptionKeys.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("No suitable encryption key found in key ring " + new OpenPgpV4Fingerprint(ring));
|
||||||
}
|
}
|
||||||
|
EncryptionBuilder.this.encryptionKeys.putAll(encryptionKeys);
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -305,82 +241,37 @@ public class EncryptionBuilder implements EncryptionBuilderInterface {
|
||||||
return new ArmorImpl();
|
return new ArmorImpl();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public DocumentType signWith(@Nonnull SecretKeyRingProtector decryptor, @Nonnull PGPSecretKey... keys) {
|
|
||||||
return new SignWithImpl().signWith(decryptor, keys);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DocumentType signWith(@Nonnull SecretKeyRingProtector decryptor, @Nonnull PGPSecretKeyRing... keyRings) {
|
public DocumentType signWith(@Nonnull SecretKeyRingProtector decryptor, @Nonnull PGPSecretKeyRing... keyRings) {
|
||||||
return new SignWithImpl().signWith(decryptor, keyRings);
|
return new SignWithImpl().signWith(decryptor, keyRings);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public <O> DocumentType signWith(@Nonnull SecretKeyRingSelectionStrategy<O> selectionStrategy,
|
|
||||||
@Nonnull SecretKeyRingProtector decryptor,
|
|
||||||
@Nonnull MultiMap<O, PGPSecretKeyRingCollection> keys)
|
|
||||||
throws SecretKeyNotFoundException {
|
|
||||||
return new SignWithImpl().signWith(selectionStrategy, decryptor, keys);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class SignWithImpl implements SignWith {
|
class SignWithImpl implements SignWith {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DocumentType signWith(@Nonnull SecretKeyRingProtector decryptor,
|
public DocumentType signWith(@Nonnull SecretKeyRingProtector decryptor,
|
||||||
@Nonnull PGPSecretKey... keys) {
|
@Nonnull PGPSecretKeyRing... keyRings) {
|
||||||
if (keys.length == 0) {
|
if (keyRings.length == 0) {
|
||||||
throw new IllegalArgumentException("Recipient list MUST NOT be empty.");
|
throw new IllegalArgumentException("Recipient list MUST NOT be empty.");
|
||||||
}
|
}
|
||||||
for (PGPSecretKey s : keys) {
|
for (PGPSecretKeyRing ring : keyRings) {
|
||||||
if (EncryptionBuilder.this.signingKeySelector().accept(s)) {
|
Map<SubkeyIdentifier, PGPSecretKeyRing> signingKeys = new ConcurrentHashMap<>();
|
||||||
signingKeys.add(s);
|
for (Iterator<PGPSecretKey> i = ring.getSecretKeys(); i.hasNext(); ) {
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("Key " + s.getKeyID() + " is not a valid signing key.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
EncryptionBuilder.this.signingKeysDecryptor = decryptor;
|
|
||||||
return new DocumentTypeImpl();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DocumentType signWith(@Nonnull SecretKeyRingProtector decryptor,
|
|
||||||
@Nonnull PGPSecretKeyRing... keys) {
|
|
||||||
if (keys.length == 0) {
|
|
||||||
throw new IllegalArgumentException("Recipient list MUST NOT be empty.");
|
|
||||||
}
|
|
||||||
for (PGPSecretKeyRing key : keys) {
|
|
||||||
for (Iterator<PGPSecretKey> i = key.getSecretKeys(); i.hasNext(); ) {
|
|
||||||
PGPSecretKey s = i.next();
|
PGPSecretKey s = i.next();
|
||||||
if (EncryptionBuilder.this.signingKeySelector().accept(s)) {
|
if (EncryptionBuilder.this.signingKeySelector().accept(s)) {
|
||||||
EncryptionBuilder.this.signingKeys.add(s);
|
signingKeys.put(new SubkeyIdentifier(ring, s.getKeyID()), ring);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
EncryptionBuilder.this.signingKeysDecryptor = decryptor;
|
|
||||||
return new DocumentTypeImpl();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
if (signingKeys.isEmpty()) {
|
||||||
public <O> DocumentType signWith(@Nonnull SecretKeyRingSelectionStrategy<O> ringSelectionStrategy,
|
throw new IllegalArgumentException("No suitable signing key found in key ring " + new OpenPgpV4Fingerprint(ring));
|
||||||
@Nonnull SecretKeyRingProtector decryptor,
|
|
||||||
@Nonnull MultiMap<O, PGPSecretKeyRingCollection> keys) {
|
|
||||||
if (keys.isEmpty()) {
|
|
||||||
throw new IllegalArgumentException("Recipient list MUST NOT be empty.");
|
|
||||||
}
|
|
||||||
MultiMap<O, PGPSecretKeyRing> acceptedKeyRings =
|
|
||||||
ringSelectionStrategy.selectKeyRingsFromCollections(keys);
|
|
||||||
for (O identifier : acceptedKeyRings.keySet()) {
|
|
||||||
Set<PGPSecretKeyRing> acceptedSet = acceptedKeyRings.get(identifier);
|
|
||||||
for (PGPSecretKeyRing k : acceptedSet) {
|
|
||||||
for (Iterator<PGPSecretKey> i = k.getSecretKeys(); i.hasNext(); ) {
|
|
||||||
PGPSecretKey s = i.next();
|
|
||||||
if (EncryptionBuilder.this.<O>signingKeySelector().accept(s)) {
|
|
||||||
EncryptionBuilder.this.signingKeys.add(s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EncryptionBuilder.this.signingKeys.putAll(signingKeys);
|
||||||
}
|
}
|
||||||
|
EncryptionBuilder.this.signingKeysDecryptor = decryptor;
|
||||||
return new DocumentTypeImpl();
|
return new DocumentTypeImpl();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -416,11 +307,13 @@ public class EncryptionBuilder implements EncryptionBuilderInterface {
|
||||||
|
|
||||||
private EncryptionStream build() throws IOException, PGPException {
|
private EncryptionStream build() throws IOException, PGPException {
|
||||||
|
|
||||||
Map<OpenPgpV4Fingerprint, PGPPrivateKey> privateKeys = new ConcurrentHashMap<>();
|
Map<SubkeyIdentifier, Tuple<PGPSecretKeyRing, PGPPrivateKey>> privateKeys = new ConcurrentHashMap<>();
|
||||||
for (PGPSecretKey secretKey : signingKeys) {
|
for (SubkeyIdentifier signingKey : signingKeys.keySet()) {
|
||||||
|
PGPSecretKeyRing secretKeyRing = signingKeys.get(signingKey);
|
||||||
|
PGPSecretKey secretKey = secretKeyRing.getSecretKey(signingKey.getSubkeyFingerprint().getKeyId());
|
||||||
PBESecretKeyDecryptor decryptor = signingKeysDecryptor.getDecryptor(secretKey.getKeyID());
|
PBESecretKeyDecryptor decryptor = signingKeysDecryptor.getDecryptor(secretKey.getKeyID());
|
||||||
PGPPrivateKey privateKey = secretKey.extractPrivateKey(decryptor);
|
PGPPrivateKey privateKey = secretKey.extractPrivateKey(decryptor);
|
||||||
privateKeys.put(new OpenPgpV4Fingerprint(secretKey), privateKey);
|
privateKeys.put(signingKey, new Tuple<>(secretKeyRing, privateKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
return new EncryptionStream(
|
return new EncryptionStream(
|
||||||
|
|
|
@ -15,29 +15,21 @@
|
||||||
*/
|
*/
|
||||||
package org.pgpainless.encryption_signing;
|
package org.pgpainless.encryption_signing;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
import org.bouncycastle.openpgp.PGPException;
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
|
||||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||||
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
|
||||||
import org.pgpainless.algorithm.CompressionAlgorithm;
|
import org.pgpainless.algorithm.CompressionAlgorithm;
|
||||||
import org.pgpainless.algorithm.HashAlgorithm;
|
import org.pgpainless.algorithm.HashAlgorithm;
|
||||||
import org.pgpainless.algorithm.StreamEncoding;
|
import org.pgpainless.algorithm.StreamEncoding;
|
||||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||||
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
||||||
import org.pgpainless.exception.SecretKeyNotFoundException;
|
|
||||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||||
import org.pgpainless.key.protection.UnprotectedKeysProtector;
|
|
||||||
import org.pgpainless.util.selection.keyring.PublicKeyRingSelectionStrategy;
|
|
||||||
import org.pgpainless.util.selection.keyring.SecretKeyRingSelectionStrategy;
|
|
||||||
import org.pgpainless.util.MultiMap;
|
|
||||||
import org.pgpainless.util.Passphrase;
|
import org.pgpainless.util.Passphrase;
|
||||||
|
|
||||||
public interface EncryptionBuilderInterface {
|
public interface EncryptionBuilderInterface {
|
||||||
|
@ -93,14 +85,6 @@ public interface EncryptionBuilderInterface {
|
||||||
|
|
||||||
interface ToRecipients {
|
interface ToRecipients {
|
||||||
|
|
||||||
/**
|
|
||||||
* Pass in a list of trusted public keys of the recipients.
|
|
||||||
*
|
|
||||||
* @param keys recipient keys for which the message will be encrypted.
|
|
||||||
* @return api handle
|
|
||||||
*/
|
|
||||||
WithAlgorithms toRecipients(@Nonnull PGPPublicKey... keys);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pass in a list of trusted public key rings of the recipients.
|
* Pass in a list of trusted public key rings of the recipients.
|
||||||
*
|
*
|
||||||
|
@ -117,17 +101,6 @@ public interface EncryptionBuilderInterface {
|
||||||
*/
|
*/
|
||||||
WithAlgorithms toRecipients(@Nonnull PGPPublicKeyRingCollection... keys);
|
WithAlgorithms toRecipients(@Nonnull PGPPublicKeyRingCollection... keys);
|
||||||
|
|
||||||
/**
|
|
||||||
* Pass in a map of recipient key ring collections along with a strategy for key selection.
|
|
||||||
*
|
|
||||||
* @param selectionStrategy selection strategy that is used to select suitable encryption keys.
|
|
||||||
* @param keys public keys
|
|
||||||
* @param <O> selection criteria type (eg. email address) on which the selection strategy is based
|
|
||||||
* @return api handle
|
|
||||||
*/
|
|
||||||
<O> WithAlgorithms toRecipients(@Nonnull PublicKeyRingSelectionStrategy<O> selectionStrategy,
|
|
||||||
@Nonnull MultiMap<O, PGPPublicKeyRingCollection> keys);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encrypt to one or more symmetric passphrases.
|
* Encrypt to one or more symmetric passphrases.
|
||||||
* Note that the passphrases MUST NOT be empty.
|
* Note that the passphrases MUST NOT be empty.
|
||||||
|
@ -148,14 +121,6 @@ public interface EncryptionBuilderInterface {
|
||||||
|
|
||||||
interface WithAlgorithms {
|
interface WithAlgorithms {
|
||||||
|
|
||||||
/**
|
|
||||||
* Add our own public key to the list of recipient keys.
|
|
||||||
*
|
|
||||||
* @param keys own public keys
|
|
||||||
* @return api handle
|
|
||||||
*/
|
|
||||||
WithAlgorithms andToSelf(@Nonnull PGPPublicKey... keys);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add our own public key to the list of recipient keys.
|
* Add our own public key to the list of recipient keys.
|
||||||
*
|
*
|
||||||
|
@ -172,17 +137,6 @@ public interface EncryptionBuilderInterface {
|
||||||
*/
|
*/
|
||||||
WithAlgorithms andToSelf(@Nonnull PGPPublicKeyRingCollection keys);
|
WithAlgorithms andToSelf(@Nonnull PGPPublicKeyRingCollection keys);
|
||||||
|
|
||||||
/**
|
|
||||||
* Add our own public keys to the list of recipient keys.
|
|
||||||
*
|
|
||||||
* @param selectionStrategy key selection strategy used to determine suitable keys for encryption.
|
|
||||||
* @param keys public keys
|
|
||||||
* @param <O> selection criteria type (eg. email address) used by the selection strategy.
|
|
||||||
* @return api handle
|
|
||||||
*/
|
|
||||||
<O> WithAlgorithms andToSelf(@Nonnull PublicKeyRingSelectionStrategy<O> selectionStrategy,
|
|
||||||
@Nonnull MultiMap<O, PGPPublicKeyRingCollection> keys);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specify which algorithms should be used for the encryption.
|
* Specify which algorithms should be used for the encryption.
|
||||||
*
|
*
|
||||||
|
@ -227,28 +181,6 @@ public interface EncryptionBuilderInterface {
|
||||||
|
|
||||||
interface SignWith {
|
interface SignWith {
|
||||||
|
|
||||||
/**
|
|
||||||
* Pass in a list of secret keys used for signing.
|
|
||||||
* Those keys are considered unlocked (ie. not password protected).
|
|
||||||
* If you need to use password protected keys instead, use {@link #signWith(SecretKeyRingProtector, PGPSecretKey...)}.
|
|
||||||
*
|
|
||||||
* @param keys secret keys
|
|
||||||
* @return api handle
|
|
||||||
*/
|
|
||||||
default DocumentType signWith(@Nonnull PGPSecretKey... keys) {
|
|
||||||
return signWith(new UnprotectedKeysProtector(), keys);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pass in a list of secret keys used for signing, along with a {@link SecretKeyRingProtector} used to unlock
|
|
||||||
* the secret keys.
|
|
||||||
*
|
|
||||||
* @param decryptor {@link SecretKeyRingProtector} used to unlock the secret keys
|
|
||||||
* @param keys secret keys used for signing
|
|
||||||
* @return api handle
|
|
||||||
*/
|
|
||||||
DocumentType signWith(@Nonnull SecretKeyRingProtector decryptor, @Nonnull PGPSecretKey... keys);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pass in a list of secret keys used for signing, along with a {@link SecretKeyRingProtector} used to unlock
|
* Pass in a list of secret keys used for signing, along with a {@link SecretKeyRingProtector} used to unlock
|
||||||
* the secret keys.
|
* the secret keys.
|
||||||
|
@ -259,24 +191,6 @@ public interface EncryptionBuilderInterface {
|
||||||
*/
|
*/
|
||||||
DocumentType signWith(@Nonnull SecretKeyRingProtector decryptor, @Nonnull PGPSecretKeyRing... keyRings);
|
DocumentType signWith(@Nonnull SecretKeyRingProtector decryptor, @Nonnull PGPSecretKeyRing... keyRings);
|
||||||
|
|
||||||
/**
|
|
||||||
* Pass in a map of secret keys for signing, as well as a {@link org.pgpainless.util.selection.key.SecretKeySelectionStrategy}
|
|
||||||
* that is used to determine suitable secret keys.
|
|
||||||
* If the keys are locked by a password, the provided {@link SecretKeyRingProtector} will be used to unlock the keys.
|
|
||||||
*
|
|
||||||
* @param selectionStrategy key selection strategy
|
|
||||||
* @param decryptor decryptor for unlocking secret keys
|
|
||||||
* @param keys secret keys
|
|
||||||
* @param <O> selection criteria type (eg. email address)
|
|
||||||
* @return api handle
|
|
||||||
*
|
|
||||||
* @throws SecretKeyNotFoundException in case no suitable secret key can be found
|
|
||||||
*/
|
|
||||||
<O> DocumentType signWith(@Nonnull SecretKeyRingSelectionStrategy<O> selectionStrategy,
|
|
||||||
@Nonnull SecretKeyRingProtector decryptor,
|
|
||||||
@Nonnull MultiMap<O, PGPSecretKeyRingCollection> keys)
|
|
||||||
throws SecretKeyNotFoundException;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DocumentType {
|
interface DocumentType {
|
||||||
|
|
|
@ -33,6 +33,8 @@ import org.bouncycastle.openpgp.PGPException;
|
||||||
import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
|
import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
|
||||||
import org.bouncycastle.openpgp.PGPPrivateKey;
|
import org.bouncycastle.openpgp.PGPPrivateKey;
|
||||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||||
|
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||||
import org.bouncycastle.openpgp.PGPSignature;
|
import org.bouncycastle.openpgp.PGPSignature;
|
||||||
import org.bouncycastle.openpgp.PGPSignatureGenerator;
|
import org.bouncycastle.openpgp.PGPSignatureGenerator;
|
||||||
import org.bouncycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator;
|
import org.bouncycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator;
|
||||||
|
@ -45,12 +47,13 @@ import org.pgpainless.algorithm.CompressionAlgorithm;
|
||||||
import org.pgpainless.algorithm.HashAlgorithm;
|
import org.pgpainless.algorithm.HashAlgorithm;
|
||||||
import org.pgpainless.algorithm.SignatureType;
|
import org.pgpainless.algorithm.SignatureType;
|
||||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||||
import org.pgpainless.decryption_verification.DetachedSignature;
|
|
||||||
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
||||||
import org.pgpainless.implementation.ImplementationFactory;
|
import org.pgpainless.implementation.ImplementationFactory;
|
||||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
import org.pgpainless.key.SubkeyIdentifier;
|
||||||
|
import org.pgpainless.signature.DetachedSignature;
|
||||||
import org.pgpainless.util.ArmoredOutputStreamFactory;
|
import org.pgpainless.util.ArmoredOutputStreamFactory;
|
||||||
import org.pgpainless.util.Passphrase;
|
import org.pgpainless.util.Passphrase;
|
||||||
|
import org.pgpainless.util.Tuple;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is based upon Jens Neuhalfen's Bouncy-GPG PGPEncryptingStream.
|
* This class is based upon Jens Neuhalfen's Bouncy-GPG PGPEncryptingStream.
|
||||||
|
@ -83,16 +86,16 @@ public final class EncryptionStream extends OutputStream {
|
||||||
private final SymmetricKeyAlgorithm symmetricKeyAlgorithm;
|
private final SymmetricKeyAlgorithm symmetricKeyAlgorithm;
|
||||||
private final HashAlgorithm hashAlgorithm;
|
private final HashAlgorithm hashAlgorithm;
|
||||||
private final CompressionAlgorithm compressionAlgorithm;
|
private final CompressionAlgorithm compressionAlgorithm;
|
||||||
private final Set<PGPPublicKey> encryptionKeys;
|
private final Map<SubkeyIdentifier, PGPPublicKeyRing> encryptionKeys;
|
||||||
private final Set<Passphrase> encryptionPassphrases;
|
private final Set<Passphrase> encryptionPassphrases;
|
||||||
private final boolean detachedSignature;
|
private final boolean detachedSignature;
|
||||||
private final SignatureType signatureType;
|
private final SignatureType signatureType;
|
||||||
private final Map<OpenPgpV4Fingerprint, PGPPrivateKey> signingKeys;
|
private final Map<SubkeyIdentifier, Tuple<PGPSecretKeyRing, PGPPrivateKey>> signingKeys;
|
||||||
private final boolean asciiArmor;
|
private final boolean asciiArmor;
|
||||||
|
|
||||||
private final OpenPgpMetadata.Builder resultBuilder = OpenPgpMetadata.getBuilder();
|
private final OpenPgpMetadata.Builder resultBuilder = OpenPgpMetadata.getBuilder();
|
||||||
|
|
||||||
private Map<OpenPgpV4Fingerprint, PGPSignatureGenerator> signatureGenerators = new ConcurrentHashMap<>();
|
private Map<SubkeyIdentifier, Tuple<PGPSecretKeyRing, PGPSignatureGenerator>> signatureGenerators = new ConcurrentHashMap<>();
|
||||||
private boolean closed = false;
|
private boolean closed = false;
|
||||||
|
|
||||||
OutputStream outermostStream = null;
|
OutputStream outermostStream = null;
|
||||||
|
@ -107,11 +110,11 @@ public final class EncryptionStream extends OutputStream {
|
||||||
private OutputStream literalDataStream;
|
private OutputStream literalDataStream;
|
||||||
|
|
||||||
EncryptionStream(@Nonnull OutputStream targetOutputStream,
|
EncryptionStream(@Nonnull OutputStream targetOutputStream,
|
||||||
@Nonnull Set<PGPPublicKey> encryptionKeys,
|
@Nonnull Map<SubkeyIdentifier, PGPPublicKeyRing> encryptionKeys,
|
||||||
@Nonnull Set<Passphrase> encryptionPassphrases,
|
@Nonnull Set<Passphrase> encryptionPassphrases,
|
||||||
boolean detachedSignature,
|
boolean detachedSignature,
|
||||||
SignatureType signatureType,
|
SignatureType signatureType,
|
||||||
@Nonnull Map<OpenPgpV4Fingerprint, PGPPrivateKey> signingKeys,
|
@Nonnull Map<SubkeyIdentifier, Tuple<PGPSecretKeyRing, PGPPrivateKey>> signingKeys,
|
||||||
@Nonnull SymmetricKeyAlgorithm symmetricKeyAlgorithm,
|
@Nonnull SymmetricKeyAlgorithm symmetricKeyAlgorithm,
|
||||||
@Nonnull HashAlgorithm hashAlgorithm,
|
@Nonnull HashAlgorithm hashAlgorithm,
|
||||||
@Nonnull CompressionAlgorithm compressionAlgorithm,
|
@Nonnull CompressionAlgorithm compressionAlgorithm,
|
||||||
|
@ -122,7 +125,7 @@ public final class EncryptionStream extends OutputStream {
|
||||||
this.symmetricKeyAlgorithm = symmetricKeyAlgorithm;
|
this.symmetricKeyAlgorithm = symmetricKeyAlgorithm;
|
||||||
this.hashAlgorithm = hashAlgorithm;
|
this.hashAlgorithm = hashAlgorithm;
|
||||||
this.compressionAlgorithm = compressionAlgorithm;
|
this.compressionAlgorithm = compressionAlgorithm;
|
||||||
this.encryptionKeys = Collections.unmodifiableSet(encryptionKeys);
|
this.encryptionKeys = Collections.unmodifiableMap(encryptionKeys);
|
||||||
this.encryptionPassphrases = Collections.unmodifiableSet(encryptionPassphrases);
|
this.encryptionPassphrases = Collections.unmodifiableSet(encryptionPassphrases);
|
||||||
this.detachedSignature = detachedSignature;
|
this.detachedSignature = detachedSignature;
|
||||||
this.signatureType = signatureType;
|
this.signatureType = signatureType;
|
||||||
|
@ -170,8 +173,9 @@ public final class EncryptionStream extends OutputStream {
|
||||||
PGPEncryptedDataGenerator encryptedDataGenerator =
|
PGPEncryptedDataGenerator encryptedDataGenerator =
|
||||||
new PGPEncryptedDataGenerator(dataEncryptorBuilder);
|
new PGPEncryptedDataGenerator(dataEncryptorBuilder);
|
||||||
|
|
||||||
for (PGPPublicKey key : encryptionKeys) {
|
for (SubkeyIdentifier keyIdentifier : encryptionKeys.keySet()) {
|
||||||
LOGGER.log(LEVEL, "Encrypt for key " + Long.toHexString(key.getKeyID()));
|
LOGGER.log(LEVEL, "Encrypt for key " + keyIdentifier);
|
||||||
|
PGPPublicKey key = encryptionKeys.get(keyIdentifier).getPublicKey(keyIdentifier.getSubkeyFingerprint().getKeyId());
|
||||||
PublicKeyKeyEncryptionMethodGenerator keyEncryption =
|
PublicKeyKeyEncryptionMethodGenerator keyEncryption =
|
||||||
ImplementationFactory.getInstance().getPublicKeyKeyEncryptionMethodGenerator(key);
|
ImplementationFactory.getInstance().getPublicKeyKeyEncryptionMethodGenerator(key);
|
||||||
encryptedDataGenerator.addMethod(keyEncryption);
|
encryptedDataGenerator.addMethod(keyEncryption);
|
||||||
|
@ -193,17 +197,17 @@ public final class EncryptionStream extends OutputStream {
|
||||||
}
|
}
|
||||||
|
|
||||||
LOGGER.log(LEVEL, "At least one signing key is available -> sign " + hashAlgorithm + " hash of message");
|
LOGGER.log(LEVEL, "At least one signing key is available -> sign " + hashAlgorithm + " hash of message");
|
||||||
for (OpenPgpV4Fingerprint fingerprint : signingKeys.keySet()) {
|
for (SubkeyIdentifier subkeyIdentifier : signingKeys.keySet()) {
|
||||||
PGPPrivateKey privateKey = signingKeys.get(fingerprint);
|
LOGGER.log(LEVEL, "Sign using key " + subkeyIdentifier);
|
||||||
LOGGER.log(LEVEL, "Sign using key " + fingerprint);
|
|
||||||
|
PGPPrivateKey privateKey = signingKeys.get(subkeyIdentifier).getSecond();
|
||||||
PGPContentSignerBuilder contentSignerBuilder = ImplementationFactory.getInstance()
|
PGPContentSignerBuilder contentSignerBuilder = ImplementationFactory.getInstance()
|
||||||
.getPGPContentSignerBuilder(
|
.getPGPContentSignerBuilder(
|
||||||
privateKey.getPublicKeyPacket().getAlgorithm(),
|
privateKey.getPublicKeyPacket().getAlgorithm(),
|
||||||
hashAlgorithm.getAlgorithmId());
|
hashAlgorithm.getAlgorithmId());
|
||||||
|
|
||||||
PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder);
|
PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder);
|
||||||
signatureGenerator.init(signatureType.getCode(), privateKey);
|
signatureGenerator.init(signatureType.getCode(), privateKey);
|
||||||
signatureGenerators.put(fingerprint, signatureGenerator);
|
signatureGenerators.put(subkeyIdentifier, new Tuple<>(signingKeys.get(subkeyIdentifier).getFirst(), signatureGenerator));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,7 +224,8 @@ public final class EncryptionStream extends OutputStream {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void prepareOnePassSignatures() throws IOException, PGPException {
|
private void prepareOnePassSignatures() throws IOException, PGPException {
|
||||||
for (PGPSignatureGenerator signatureGenerator : signatureGenerators.values()) {
|
for (SubkeyIdentifier identifier : signatureGenerators.keySet()) {
|
||||||
|
PGPSignatureGenerator signatureGenerator = signatureGenerators.get(identifier).getSecond();
|
||||||
signatureGenerator.generateOnePassVersion(false).encode(outermostStream);
|
signatureGenerator.generateOnePassVersion(false).encode(outermostStream);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -236,8 +241,8 @@ public final class EncryptionStream extends OutputStream {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void prepareResultBuilder() {
|
private void prepareResultBuilder() {
|
||||||
for (PGPPublicKey recipient : encryptionKeys) {
|
for (SubkeyIdentifier recipient : encryptionKeys.keySet()) {
|
||||||
resultBuilder.addRecipientKeyId(recipient.getKeyID());
|
resultBuilder.addRecipientKeyId(recipient.getSubkeyFingerprint().getKeyId());
|
||||||
}
|
}
|
||||||
resultBuilder.setSymmetricKeyAlgorithm(symmetricKeyAlgorithm);
|
resultBuilder.setSymmetricKeyAlgorithm(symmetricKeyAlgorithm);
|
||||||
resultBuilder.setCompressionAlgorithm(compressionAlgorithm);
|
resultBuilder.setCompressionAlgorithm(compressionAlgorithm);
|
||||||
|
@ -247,7 +252,8 @@ public final class EncryptionStream extends OutputStream {
|
||||||
public void write(int data) throws IOException {
|
public void write(int data) throws IOException {
|
||||||
outermostStream.write(data);
|
outermostStream.write(data);
|
||||||
|
|
||||||
for (PGPSignatureGenerator signatureGenerator : signatureGenerators.values()) {
|
for (SubkeyIdentifier signingKey : signatureGenerators.keySet()) {
|
||||||
|
PGPSignatureGenerator signatureGenerator = signatureGenerators.get(signingKey).getSecond();
|
||||||
byte asByte = (byte) (data & 0xff);
|
byte asByte = (byte) (data & 0xff);
|
||||||
signatureGenerator.update(asByte);
|
signatureGenerator.update(asByte);
|
||||||
}
|
}
|
||||||
|
@ -262,7 +268,8 @@ public final class EncryptionStream extends OutputStream {
|
||||||
@Override
|
@Override
|
||||||
public void write(byte[] buffer, int off, int len) throws IOException {
|
public void write(byte[] buffer, int off, int len) throws IOException {
|
||||||
outermostStream.write(buffer, 0, len);
|
outermostStream.write(buffer, 0, len);
|
||||||
for (PGPSignatureGenerator signatureGenerator : signatureGenerators.values()) {
|
for (SubkeyIdentifier signingKey : signatureGenerators.keySet()) {
|
||||||
|
PGPSignatureGenerator signatureGenerator = signatureGenerators.get(signingKey).getSecond();
|
||||||
signatureGenerator.update(buffer, 0, len);
|
signatureGenerator.update(buffer, 0, len);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -303,14 +310,16 @@ public final class EncryptionStream extends OutputStream {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeSignatures() throws IOException {
|
private void writeSignatures() throws IOException {
|
||||||
for (OpenPgpV4Fingerprint fingerprint : signatureGenerators.keySet()) {
|
for (SubkeyIdentifier signingKey : signatureGenerators.keySet()) {
|
||||||
PGPSignatureGenerator signatureGenerator = signatureGenerators.get(fingerprint);
|
PGPSignatureGenerator signatureGenerator = signatureGenerators.get(signingKey).getSecond();
|
||||||
try {
|
try {
|
||||||
PGPSignature signature = signatureGenerator.generate();
|
PGPSignature signature = signatureGenerator.generate();
|
||||||
if (!detachedSignature) {
|
if (!detachedSignature) {
|
||||||
signature.encode(outermostStream);
|
signature.encode(outermostStream);
|
||||||
}
|
}
|
||||||
resultBuilder.addDetachedSignature(new DetachedSignature(signature, fingerprint));
|
DetachedSignature detachedSignature = new DetachedSignature(
|
||||||
|
signature, signatureGenerators.get(signingKey).getFirst(), signingKey);
|
||||||
|
resultBuilder.addDetachedSignature(detachedSignature);
|
||||||
} catch (PGPException e) {
|
} catch (PGPException e) {
|
||||||
throw new IOException(e);
|
throw new IOException(e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,260 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 Paul Schaub.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.pgpainless.key;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
|
import org.bouncycastle.openpgp.PGPKeyRing;
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||||
|
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||||
|
import org.bouncycastle.openpgp.PGPSignature;
|
||||||
|
import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVector;
|
||||||
|
import org.pgpainless.algorithm.SignatureType;
|
||||||
|
import org.pgpainless.implementation.ImplementationFactory;
|
||||||
|
import org.pgpainless.key.info.KeyRingInfo;
|
||||||
|
import org.pgpainless.key.util.KeyRingUtils;
|
||||||
|
import org.pgpainless.policy.Policy;
|
||||||
|
import org.pgpainless.signature.SelectSignatureFromKey;
|
||||||
|
import org.pgpainless.signature.SignatureCreationDateComparator;
|
||||||
|
import org.pgpainless.signature.SignatureValidationException;
|
||||||
|
import org.pgpainless.signature.SignatureValidator;
|
||||||
|
import org.pgpainless.util.CollectionUtils;
|
||||||
|
|
||||||
|
public class KeyRingValidator {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(KeyRingValidator.class.getName());
|
||||||
|
|
||||||
|
public static <R extends PGPKeyRing> R validate(R keyRing, Policy policy) {
|
||||||
|
try {
|
||||||
|
return validate(keyRing, policy, policy.getSignatureValidationDate());
|
||||||
|
} catch (PGPException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <R extends PGPKeyRing> R validate(R keyRing, Policy policy, Date validationDate) throws PGPException {
|
||||||
|
return getKeyRingAtDate(keyRing, policy, validationDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <R extends PGPKeyRing> R getKeyRingAtDate(R keyRing, Policy policy, Date validationDate) throws PGPException {
|
||||||
|
PGPPublicKey primaryKey = keyRing.getPublicKey();
|
||||||
|
primaryKey = evaluatePrimaryKey(primaryKey, policy, validationDate);
|
||||||
|
if (keyRing instanceof PGPPublicKeyRing) {
|
||||||
|
PGPPublicKeyRing publicKeys = (PGPPublicKeyRing) keyRing;
|
||||||
|
publicKeys = PGPPublicKeyRing.insertPublicKey(publicKeys, primaryKey);
|
||||||
|
keyRing = (R) publicKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
return keyRing;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PGPPublicKey evaluatePrimaryKey(PGPPublicKey primaryKey, Policy policy, Date validationDate) throws PGPException {
|
||||||
|
|
||||||
|
PGPPublicKey blank = new PGPPublicKey(primaryKey.getPublicKeyPacket(), ImplementationFactory.getInstance().getKeyFingerprintCalculator());
|
||||||
|
|
||||||
|
Iterator<PGPSignature> directKeyIterator = primaryKey.getSignaturesOfType(SignatureType.DIRECT_KEY.getCode());
|
||||||
|
List<PGPSignature> directKeyCertifications = CollectionUtils.iteratorToList(directKeyIterator);
|
||||||
|
Collections.sort(directKeyCertifications, new SignatureCreationDateComparator(SignatureCreationDateComparator.Order.new_to_old));
|
||||||
|
for (PGPSignature signature : directKeyCertifications) {
|
||||||
|
try {
|
||||||
|
if (SignatureValidator.verifyDirectKeySignature(signature, blank, policy, validationDate)) {
|
||||||
|
blank = PGPPublicKey.addCertification(blank, signature);
|
||||||
|
}
|
||||||
|
} catch (SignatureValidationException e) {
|
||||||
|
LOGGER.log(Level.INFO, "Rejecting direct key signature", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterator<PGPSignature> revocationIterator = primaryKey.getSignaturesOfType(SignatureType.KEY_REVOCATION.getCode());
|
||||||
|
List<PGPSignature> directKeyRevocations = CollectionUtils.iteratorToList(revocationIterator);
|
||||||
|
Collections.sort(directKeyRevocations, new SignatureCreationDateComparator(SignatureCreationDateComparator.Order.new_to_old));
|
||||||
|
for (PGPSignature signature : directKeyRevocations) {
|
||||||
|
try {
|
||||||
|
if (SignatureValidator.verifyKeyRevocationSignature(signature, primaryKey, policy, validationDate)) {
|
||||||
|
blank = PGPPublicKey.addCertification(blank, signature);
|
||||||
|
}
|
||||||
|
} catch (SignatureValidationException e) {
|
||||||
|
LOGGER.log(Level.INFO, "Rejecting key revocation signature", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterator<String> userIdIterator = primaryKey.getUserIDs();
|
||||||
|
while (userIdIterator.hasNext()) {
|
||||||
|
String userId = userIdIterator.next();
|
||||||
|
Iterator<PGPSignature> userIdSigs = primaryKey.getSignaturesForID(userId);
|
||||||
|
List<PGPSignature> signatures = CollectionUtils.iteratorToList(userIdSigs);
|
||||||
|
Collections.sort(signatures, new SignatureCreationDateComparator(SignatureCreationDateComparator.Order.new_to_old));
|
||||||
|
for (PGPSignature signature : signatures) {
|
||||||
|
try {
|
||||||
|
if (SignatureType.valueOf(signature.getSignatureType()) == SignatureType.CERTIFICATION_REVOCATION) {
|
||||||
|
if (SignatureValidator.verifyUserIdRevocation(userId, signature, primaryKey, policy, validationDate)) {
|
||||||
|
blank = PGPPublicKey.addCertification(blank, userId, signature);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (SignatureValidator.verifyUserIdCertification(userId, signature, primaryKey, policy, validationDate)) {
|
||||||
|
blank = PGPPublicKey.addCertification(blank, userId, signature);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SignatureValidationException e) {
|
||||||
|
LOGGER.log(Level.INFO, "Rejecting user-id certification for user-id " + userId, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterator<PGPUserAttributeSubpacketVector> userAttributes = primaryKey.getUserAttributes();
|
||||||
|
while (userAttributes.hasNext()) {
|
||||||
|
PGPUserAttributeSubpacketVector userAttribute = userAttributes.next();
|
||||||
|
Iterator<PGPSignature> userAttributeSignatureIterator = primaryKey.getSignaturesForUserAttribute(userAttribute);
|
||||||
|
while (userAttributeSignatureIterator.hasNext()) {
|
||||||
|
PGPSignature signature = userAttributeSignatureIterator.next();
|
||||||
|
try {
|
||||||
|
if (SignatureType.valueOf(signature.getSignatureType()) == SignatureType.CERTIFICATION_REVOCATION) {
|
||||||
|
if (SignatureValidator.verifyUserAttributesRevocation(userAttribute, signature, primaryKey, policy, validationDate)) {
|
||||||
|
blank = PGPPublicKey.addCertification(blank, userAttribute, signature);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (SignatureValidator.verifyUserAttributesCertification(userAttribute, signature, primaryKey, policy, validationDate)) {
|
||||||
|
blank = PGPPublicKey.addCertification(blank, userAttribute, signature);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SignatureValidationException e) {
|
||||||
|
LOGGER.log(Level.INFO, "Rejecting user-attribute signature", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return blank;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <R extends PGPKeyRing> R getKeyRingAtDate(R keyRing, KeyRingInfo info) {
|
||||||
|
Iterator<PGPPublicKey> iterator = keyRing.getPublicKeys();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
PGPPublicKey publicKey = iterator.next();
|
||||||
|
if (publicKey.isMasterKey()) {
|
||||||
|
keyRing = assessPrimaryKeyAtDate(publicKey, keyRing, info);
|
||||||
|
} else {
|
||||||
|
keyRing = assessSubkeyAtDate(publicKey, keyRing, info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return keyRing;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <R extends PGPKeyRing> R assessPrimaryKeyAtDate(PGPPublicKey primaryKey, PGPKeyRing keyRing, KeyRingInfo info) {
|
||||||
|
if (!primaryKey.isMasterKey()) {
|
||||||
|
throw new IllegalArgumentException("Passed in key is not a primary key");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Direct Key Signatures
|
||||||
|
PGPSignature latestSelfSig = info.getCurrentDirectKeySelfSignature();
|
||||||
|
PGPSignature latestSelfRevocation = info.getRevocationSelfSignature();
|
||||||
|
|
||||||
|
|
||||||
|
// User-ID certifications
|
||||||
|
Iterator<String> userIdIterator = primaryKey.getUserIDs();
|
||||||
|
while (userIdIterator.hasNext()) {
|
||||||
|
String userId = userIdIterator.next();
|
||||||
|
boolean isUserIdBound = false;
|
||||||
|
Iterator<PGPSignature> userIdSigIterator = primaryKey.getSignaturesForID(userId);
|
||||||
|
while (userIdSigIterator.hasNext()) {
|
||||||
|
PGPSignature userIdSig = userIdSigIterator.next();
|
||||||
|
if (!SelectSignatureFromKey.isValidSignatureOnUserId(userId, primaryKey)
|
||||||
|
.accept(userIdSig, primaryKey, keyRing)) {
|
||||||
|
primaryKey = PGPPublicKey.removeCertification(primaryKey, userId, userIdSig);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
isUserIdBound = true;
|
||||||
|
}
|
||||||
|
if (!isUserIdBound) {
|
||||||
|
primaryKey = PGPPublicKey.removeCertification(primaryKey, userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Revocations
|
||||||
|
Iterator<PGPSignature> revocationSignatures = primaryKey.getSignaturesOfType(SignatureType.KEY_REVOCATION.getCode());
|
||||||
|
while (revocationSignatures.hasNext()) {
|
||||||
|
PGPSignature revocationSig = revocationSignatures.next();
|
||||||
|
if (!SelectSignatureFromKey.isValidKeyRevocationSignature(primaryKey)
|
||||||
|
.accept(revocationSig, primaryKey, keyRing)) {
|
||||||
|
primaryKey = PGPPublicKey.removeCertification(primaryKey, revocationSig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (R) replacePublicKey(keyRing, primaryKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <R extends PGPKeyRing> R assessSubkeyAtDate(PGPPublicKey subkey, PGPKeyRing keyRing, KeyRingInfo info) {
|
||||||
|
if (subkey.isMasterKey()) {
|
||||||
|
throw new IllegalArgumentException("Passed in key is not a subkey");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subkey binding sigs
|
||||||
|
Iterator<PGPSignature> subkeyBindingSigIterator = subkey.getSignaturesOfType(SignatureType.SUBKEY_BINDING.getCode());
|
||||||
|
while (subkeyBindingSigIterator.hasNext()) {
|
||||||
|
PGPSignature signature = subkeyBindingSigIterator.next();
|
||||||
|
if (!SelectSignatureFromKey.isValidSubkeyBindingSignature(keyRing.getPublicKey(), subkey)
|
||||||
|
.accept(signature, subkey, keyRing)) {
|
||||||
|
subkey = PGPPublicKey.removeCertification(subkey, signature);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subkey revocation sigs
|
||||||
|
Iterator<PGPSignature> revocationSigIterator = subkey.getSignaturesOfType(SignatureType.SUBKEY_REVOCATION.getCode());
|
||||||
|
while (revocationSigIterator.hasNext()) {
|
||||||
|
PGPSignature signature = revocationSigIterator.next();
|
||||||
|
if (!SelectSignatureFromKey.isValidSubkeyRevocationSignature().accept(signature, subkey, keyRing)) {
|
||||||
|
subkey = PGPPublicKey.removeCertification(subkey, signature);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterator<PGPSignature> directKeySigIterator = subkey.getSignaturesOfType(SignatureType.DIRECT_KEY.getCode());
|
||||||
|
while (directKeySigIterator.hasNext()) {
|
||||||
|
PGPSignature signature = directKeySigIterator.next();
|
||||||
|
PGPPublicKey creator = keyRing.getPublicKey(signature.getKeyID());
|
||||||
|
if (creator == null) {
|
||||||
|
// remove external signature
|
||||||
|
subkey = PGPPublicKey.removeCertification(subkey, signature);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SelectSignatureFromKey.isValidDirectKeySignature(creator, subkey)
|
||||||
|
.accept(signature, subkey, keyRing)) {
|
||||||
|
subkey = PGPPublicKey.removeCertification(subkey, signature);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (R) replacePublicKey(keyRing, subkey);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PGPKeyRing replacePublicKey(PGPKeyRing keyRing, PGPPublicKey publicKey) {
|
||||||
|
if (keyRing instanceof PGPPublicKeyRing) {
|
||||||
|
keyRing = PGPPublicKeyRing.insertPublicKey((PGPPublicKeyRing) keyRing, publicKey);
|
||||||
|
} else if (keyRing instanceof PGPSecretKeyRing) {
|
||||||
|
PGPSecretKeyRing secretKeys = (PGPSecretKeyRing) keyRing;
|
||||||
|
PGPPublicKeyRing publicKeys = KeyRingUtils.publicKeyRingFrom(secretKeys);
|
||||||
|
publicKeys = PGPPublicKeyRing.insertPublicKey(publicKeys, publicKey);
|
||||||
|
secretKeys = PGPSecretKeyRing.replacePublicKeys(secretKeys, publicKeys);
|
||||||
|
keyRing = secretKeys;
|
||||||
|
}
|
||||||
|
return keyRing;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 Paul Schaub.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.pgpainless.key;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||||
|
import org.bouncycastle.openpgp.PGPSignature;
|
||||||
|
import org.pgpainless.algorithm.SignatureType;
|
||||||
|
import org.pgpainless.signature.SelectSignatureFromKey;
|
||||||
|
|
||||||
|
public class KeyValidator {
|
||||||
|
|
||||||
|
public PGPPublicKeyRing validatePublicKeyRing(PGPPublicKeyRing publicKeys) throws PGPException {
|
||||||
|
PGPPublicKey primaryKey = publicKeys.getPublicKey();
|
||||||
|
if (!isValidPrimaryKey(primaryKey, publicKeys)) {
|
||||||
|
throw new PGPException("Primary key is not valid");
|
||||||
|
}
|
||||||
|
return publicKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isValidPrimaryKey(PGPPublicKey publicKey, PGPPublicKeyRing keyRing) {
|
||||||
|
if (!publicKey.isMasterKey()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyRing.getPublicKey().getKeyID() != publicKey.getKeyID()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterator<PGPSignature> signatures = publicKey.getSignatures();
|
||||||
|
while (signatures.hasNext()) {
|
||||||
|
PGPSignature signature = signatures.next();
|
||||||
|
SignatureType signatureType = SignatureType.valueOf(signature.getSignatureType());
|
||||||
|
switch (signatureType) {
|
||||||
|
case KEY_REVOCATION:
|
||||||
|
if (SelectSignatureFromKey.isValidKeyRevocationSignature(publicKey).accept(signature, publicKey, keyRing)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,109 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 Paul Schaub.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.pgpainless.key;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
import org.bouncycastle.openpgp.PGPKeyRing;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tuple class used to identify a subkey by fingerprints of the primary key of the subkeys key ring,
|
||||||
|
* as well as the subkeys fingerprint.
|
||||||
|
*/
|
||||||
|
public class SubkeyIdentifier {
|
||||||
|
|
||||||
|
private final OpenPgpV4Fingerprint primaryKeyFingerprint;
|
||||||
|
private final OpenPgpV4Fingerprint subkeyFingerprint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a {@link SubkeyIdentifier} from a {@link PGPKeyRing} and the subkeys key id.
|
||||||
|
* {@link #getPrimaryKeyFingerprint()} will return the {@link OpenPgpV4Fingerprint} of the keyrings primary key,
|
||||||
|
* while {@link #getSubkeyFingerprint()} will return the subkeys fingerprint.
|
||||||
|
*
|
||||||
|
* @param keyRing keyring the subkey belongs to
|
||||||
|
* @param keyId keyid of the subkey
|
||||||
|
*/
|
||||||
|
public SubkeyIdentifier(@Nonnull PGPKeyRing keyRing, long keyId) {
|
||||||
|
this(new OpenPgpV4Fingerprint(keyRing.getPublicKey()), new OpenPgpV4Fingerprint(keyRing.getPublicKey(keyId)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a {@link SubkeyIdentifier} that identifies the primary key with the given fingerprint.
|
||||||
|
* This means, both {@link #getPrimaryKeyFingerprint()} and {@link #getSubkeyFingerprint()} return the same.
|
||||||
|
*
|
||||||
|
* @param primaryKeyFingerprint fingerprint of the identified key
|
||||||
|
*/
|
||||||
|
public SubkeyIdentifier(@Nonnull OpenPgpV4Fingerprint primaryKeyFingerprint) {
|
||||||
|
this(primaryKeyFingerprint, primaryKeyFingerprint);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a {@link SubkeyIdentifier} which points to the subkey with the given subkeyFingerprint,
|
||||||
|
* which has a primary key with the given primaryKeyFingerprint.
|
||||||
|
*
|
||||||
|
* @param primaryKeyFingerprint fingerprint of the primary key
|
||||||
|
* @param subkeyFingerprint fingerprint of the subkey
|
||||||
|
*/
|
||||||
|
public SubkeyIdentifier(@Nonnull OpenPgpV4Fingerprint primaryKeyFingerprint, @Nonnull OpenPgpV4Fingerprint subkeyFingerprint) {
|
||||||
|
this.primaryKeyFingerprint = primaryKeyFingerprint;
|
||||||
|
this.subkeyFingerprint = subkeyFingerprint;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the {@link OpenPgpV4Fingerprint} of the primary key of the identified key.
|
||||||
|
* This might be the same as {@link #getSubkeyFingerprint()} if the identified subkey is the primary key.
|
||||||
|
*
|
||||||
|
* @return primary key fingerprint
|
||||||
|
*/
|
||||||
|
public @Nonnull OpenPgpV4Fingerprint getPrimaryKeyFingerprint() {
|
||||||
|
return primaryKeyFingerprint;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the {@link OpenPgpV4Fingerprint} of the identified subkey.
|
||||||
|
*
|
||||||
|
* @return subkey fingerprint
|
||||||
|
*/
|
||||||
|
public @Nonnull OpenPgpV4Fingerprint getSubkeyFingerprint() {
|
||||||
|
return subkeyFingerprint;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return primaryKeyFingerprint.hashCode() * 31 + subkeyFingerprint.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(obj instanceof SubkeyIdentifier)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
SubkeyIdentifier other = (SubkeyIdentifier) obj;
|
||||||
|
return getPrimaryKeyFingerprint().equals(other.getPrimaryKeyFingerprint())
|
||||||
|
&& getSubkeyFingerprint().equals(other.getSubkeyFingerprint());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return getSubkeyFingerprint() + " " + getPrimaryKeyFingerprint();
|
||||||
|
}
|
||||||
|
}
|
|
@ -60,7 +60,7 @@ import org.pgpainless.key.generation.type.xdh.XDHCurve;
|
||||||
import org.pgpainless.key.util.UserId;
|
import org.pgpainless.key.util.UserId;
|
||||||
import org.pgpainless.provider.ProviderFactory;
|
import org.pgpainless.provider.ProviderFactory;
|
||||||
import org.pgpainless.util.Passphrase;
|
import org.pgpainless.util.Passphrase;
|
||||||
import org.pgpainless.util.SignatureSubpacketGeneratorUtil;
|
import org.pgpainless.signature.subpackets.SignatureSubpacketGeneratorUtil;
|
||||||
|
|
||||||
public class KeyRingBuilder implements KeyRingBuilderInterface {
|
public class KeyRingBuilder implements KeyRingBuilderInterface {
|
||||||
|
|
||||||
|
|
|
@ -57,7 +57,9 @@ public interface KeyType {
|
||||||
*
|
*
|
||||||
* @return true if the key can sign.
|
* @return true if the key can sign.
|
||||||
*/
|
*/
|
||||||
boolean canSign();
|
default boolean canSign() {
|
||||||
|
return getAlgorithm().isSigningCapable();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true if the key that is generated from this type is able to carry the CERTIFY_OTHER key flag.
|
* Return true if the key that is generated from this type is able to carry the CERTIFY_OTHER key flag.
|
||||||
|
@ -85,7 +87,9 @@ public interface KeyType {
|
||||||
*
|
*
|
||||||
* @return true if the key can encrypt communication
|
* @return true if the key can encrypt communication
|
||||||
*/
|
*/
|
||||||
boolean canEncryptCommunication();
|
default boolean canEncryptCommunication() {
|
||||||
|
return getAlgorithm().isEncryptionCapable();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true if the key that is generated from this type is able to carry the ENCRYPT_STORAGE key flag.
|
* Return true if the key that is generated from this type is able to carry the ENCRYPT_STORAGE key flag.
|
||||||
|
|
|
@ -49,14 +49,4 @@ public final class ECDH implements KeyType {
|
||||||
public AlgorithmParameterSpec getAlgorithmSpec() {
|
public AlgorithmParameterSpec getAlgorithmSpec() {
|
||||||
return new ECNamedCurveGenParameterSpec(curve.getName());
|
return new ECNamedCurveGenParameterSpec(curve.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean canSign() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean canEncryptCommunication() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,14 +51,4 @@ public final class ECDSA implements KeyType {
|
||||||
return new ECNamedCurveGenParameterSpec(curve.getName());
|
return new ECNamedCurveGenParameterSpec(curve.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean canSign() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean canEncryptCommunication() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,13 +51,4 @@ public final class EdDSA implements KeyType {
|
||||||
return new ECNamedCurveGenParameterSpec(curve.getName());
|
return new ECNamedCurveGenParameterSpec(curve.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean canSign() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean canEncryptCommunication() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,14 +52,4 @@ public final class ElGamal implements KeyType {
|
||||||
return new ElGamalParameterSpec(length.getP(), length.getG());
|
return new ElGamalParameterSpec(length.getP(), length.getG());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean canSign() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean canEncryptCommunication() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,14 +51,4 @@ public class RSA implements KeyType {
|
||||||
public AlgorithmParameterSpec getAlgorithmSpec() {
|
public AlgorithmParameterSpec getAlgorithmSpec() {
|
||||||
return new RSAKeyGenParameterSpec(length.getLength(), RSAKeyGenParameterSpec.F4);
|
return new RSAKeyGenParameterSpec(length.getLength(), RSAKeyGenParameterSpec.F4);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean canSign() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean canEncryptCommunication() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,13 +48,4 @@ public final class XDH implements KeyType {
|
||||||
return new ECNamedCurveGenParameterSpec(curve.getName());
|
return new ECNamedCurveGenParameterSpec(curve.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean canSign() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean canEncryptCommunication() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,30 +15,34 @@
|
||||||
*/
|
*/
|
||||||
package org.pgpainless.key.info;
|
package org.pgpainless.key.info;
|
||||||
|
|
||||||
import static org.pgpainless.key.util.SignatureUtils.getLatestValidSignature;
|
|
||||||
import static org.pgpainless.key.util.SignatureUtils.sortByCreationTimeAscending;
|
|
||||||
import static org.pgpainless.util.CollectionUtils.iteratorToList;
|
import static org.pgpainless.util.CollectionUtils.iteratorToList;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import org.bouncycastle.openpgp.PGPException;
|
import org.bouncycastle.bcpg.sig.KeyFlags;
|
||||||
|
import org.bouncycastle.bcpg.sig.PrimaryUserID;
|
||||||
import org.bouncycastle.openpgp.PGPKeyRing;
|
import org.bouncycastle.openpgp.PGPKeyRing;
|
||||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
import org.bouncycastle.openpgp.PGPSecretKey;
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||||
import org.bouncycastle.openpgp.PGPSignature;
|
import org.bouncycastle.openpgp.PGPSignature;
|
||||||
|
import org.pgpainless.algorithm.KeyFlag;
|
||||||
import org.pgpainless.algorithm.PublicKeyAlgorithm;
|
import org.pgpainless.algorithm.PublicKeyAlgorithm;
|
||||||
import org.pgpainless.algorithm.SignatureType;
|
|
||||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||||
import org.pgpainless.key.util.KeyRingUtils;
|
import org.pgpainless.signature.SignaturePicker;
|
||||||
import org.pgpainless.key.util.SignatureUtils;
|
import org.pgpainless.signature.SignatureUtils;
|
||||||
|
import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility class to quickly extract certain information from a {@link PGPPublicKeyRing}/{@link PGPSecretKeyRing}.
|
* Utility class to quickly extract certain information from a {@link PGPPublicKeyRing}/{@link PGPSecretKeyRing}.
|
||||||
|
@ -49,8 +53,65 @@ public class KeyRingInfo {
|
||||||
|
|
||||||
private final PGPKeyRing keys;
|
private final PGPKeyRing keys;
|
||||||
|
|
||||||
|
private final PGPSignature revocationSelfSignature;
|
||||||
|
private final PGPSignature mostRecentSelfSignature;
|
||||||
|
private final Map<String, PGPSignature> mostRecentUserIdSignatures = new ConcurrentHashMap<>();
|
||||||
|
private final Map<String, PGPSignature> mostRecentUserIdRevocations = new ConcurrentHashMap<>();
|
||||||
|
private final Map<Long, PGPSignature> mostRecentSubkeyBindings = new ConcurrentHashMap<>();
|
||||||
|
private final Map<Long, PGPSignature> mostRecentSubkeyRevocations = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluate the key ring at creation time of the given signature.
|
||||||
|
*
|
||||||
|
* @param keyRing key ring
|
||||||
|
* @param signature signature
|
||||||
|
* @return info of key ring at signature creation time
|
||||||
|
*/
|
||||||
|
public static KeyRingInfo evaluateForSignature(PGPKeyRing keyRing, PGPSignature signature) {
|
||||||
|
return new KeyRingInfo(keyRing, signature.getCreationTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluate the key ring right now.
|
||||||
|
*
|
||||||
|
* @param keys key ring
|
||||||
|
*/
|
||||||
public KeyRingInfo(PGPKeyRing keys) {
|
public KeyRingInfo(PGPKeyRing keys) {
|
||||||
|
this(keys, new Date());
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeyRingInfo(PGPKeyRing keys, Date validationDate) {
|
||||||
this.keys = keys;
|
this.keys = keys;
|
||||||
|
|
||||||
|
revocationSelfSignature = SignaturePicker.pickCurrentRevocationSelfSignature(keys, validationDate);
|
||||||
|
mostRecentSelfSignature = SignaturePicker.pickCurrentDirectKeySelfSignature(keys, validationDate);
|
||||||
|
|
||||||
|
for (Iterator<String> it = keys.getPublicKey().getUserIDs(); it.hasNext(); ) {
|
||||||
|
String userId = it.next();
|
||||||
|
PGPSignature certification = SignaturePicker.pickCurrentUserIdCertificationSignature(keys, userId, validationDate);
|
||||||
|
if (certification != null) {
|
||||||
|
mostRecentUserIdSignatures.put(userId, certification);
|
||||||
|
}
|
||||||
|
PGPSignature revocation = SignaturePicker.pickCurrentUserIdRevocationSignature(keys, userId, validationDate);
|
||||||
|
if (revocation != null) {
|
||||||
|
mostRecentUserIdRevocations.put(userId, revocation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterator<PGPPublicKey> publicKeys = keys.getPublicKeys();
|
||||||
|
publicKeys.next(); // Skip primary key
|
||||||
|
|
||||||
|
while (publicKeys.hasNext()) {
|
||||||
|
PGPPublicKey subkey = publicKeys.next();
|
||||||
|
PGPSignature bindingSig = SignaturePicker.pickCurrentSubkeyBindingSignature(keys, subkey, validationDate);
|
||||||
|
if (bindingSig != null) {
|
||||||
|
mostRecentSubkeyBindings.put(subkey.getKeyID(), bindingSig);
|
||||||
|
}
|
||||||
|
PGPSignature bindingRevocation = SignaturePicker.pickCurrentSubkeyBindingRevocationSignature(keys, subkey, validationDate);
|
||||||
|
if (bindingRevocation != null) {
|
||||||
|
mostRecentSubkeyRevocations.put(subkey.getKeyID(), bindingRevocation);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -74,6 +135,21 @@ public class KeyRingInfo {
|
||||||
return keyRing.getPublicKey(keyId);
|
return keyRing.getPublicKey(keyId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isKeyValidlyBound(long keyId) {
|
||||||
|
PGPPublicKey publicKey = keys.getPublicKey(keyId);
|
||||||
|
if (publicKey == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (publicKey == getPublicKey()) {
|
||||||
|
return revocationSelfSignature == null;
|
||||||
|
} else {
|
||||||
|
PGPSignature binding = mostRecentSubkeyBindings.get(keyId);
|
||||||
|
PGPSignature revocation = mostRecentSubkeyRevocations.get(keyId);
|
||||||
|
return binding != null && revocation == null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return all {@link PGPPublicKey PGPPublicKeys} of this key ring.
|
* Return all {@link PGPPublicKey PGPPublicKeys} of this key ring.
|
||||||
* The first key in the list being the primary key.
|
* The first key in the list being the primary key.
|
||||||
|
@ -145,15 +221,21 @@ public class KeyRingInfo {
|
||||||
return new OpenPgpV4Fingerprint(getPublicKey());
|
return new OpenPgpV4Fingerprint(getPublicKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPrimaryUserId() throws PGPException {
|
public String getPrimaryUserId() {
|
||||||
List<String> userIds = getValidUserIds();
|
String primaryUserId = null;
|
||||||
for (String userId : userIds) {
|
Date modificationDate = null;
|
||||||
PGPSignature signature = getLatestValidSignatureOnUserId(userId);
|
for (String userId : getValidUserIds()) {
|
||||||
if (signature.getHashedSubPackets().isPrimaryUserID()) {
|
PGPSignature signature = mostRecentUserIdSignatures.get(userId);
|
||||||
return userId;
|
PrimaryUserID subpacket = SignatureSubpacketsUtil.getPrimaryUserId(signature);
|
||||||
|
if (subpacket != null && subpacket.isPrimaryUserID()) {
|
||||||
|
// if there are multiple primary userIDs, return most recently signed
|
||||||
|
if (modificationDate == null || modificationDate.before(signature.getCreationTime())) {
|
||||||
|
primaryUserId = userId;
|
||||||
|
modificationDate = signature.getCreationTime();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
}
|
||||||
|
return primaryUserId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -179,15 +261,13 @@ public class KeyRingInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isUserIdValid(String userId) {
|
public boolean isUserIdValid(String userId) {
|
||||||
return isUserIdValid(getKeyId(), userId);
|
PGPSignature certification = mostRecentUserIdSignatures.get(userId);
|
||||||
}
|
PGPSignature revocation = mostRecentUserIdRevocations.get(userId);
|
||||||
|
|
||||||
public boolean isUserIdValid(long keyId, String userId) {
|
if (certification == null) {
|
||||||
try {
|
|
||||||
return SignatureUtils.isUserIdValid(getPublicKey(keyId), userId);
|
|
||||||
} catch (PGPException e) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
return revocation == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -207,6 +287,68 @@ public class KeyRingInfo {
|
||||||
return emails;
|
return emails;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PGPSignature getCurrentDirectKeySelfSignature() {
|
||||||
|
return mostRecentSelfSignature;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PGPSignature getRevocationSelfSignature() {
|
||||||
|
return revocationSelfSignature;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PGPSignature getCurrentUserIdCertification(String userId) {
|
||||||
|
return mostRecentUserIdSignatures.get(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PGPSignature getUserIdRevocation(String userId) {
|
||||||
|
return mostRecentUserIdRevocations.get(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PGPSignature getCurrentSubkeyBindingSignature(long keyId) {
|
||||||
|
return mostRecentSubkeyBindings.get(keyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PGPSignature getSubkeyRevocationSignature(long keyId) {
|
||||||
|
return mostRecentSubkeyRevocations.get(keyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<KeyFlag> getKeyFlagsOf(long keyId) {
|
||||||
|
if (getPublicKey().getKeyID() == keyId) {
|
||||||
|
|
||||||
|
if (mostRecentSelfSignature != null) {
|
||||||
|
KeyFlags flags = SignatureSubpacketsUtil.getKeyFlags(mostRecentSelfSignature);
|
||||||
|
if (flags != null) {
|
||||||
|
return KeyFlag.fromBitmask(flags.getFlags());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String primaryUserId = getPrimaryUserId();
|
||||||
|
if (primaryUserId != null) {
|
||||||
|
KeyFlags flags = SignatureSubpacketsUtil.getKeyFlags(mostRecentUserIdSignatures.get(primaryUserId));
|
||||||
|
if (flags != null) {
|
||||||
|
return KeyFlag.fromBitmask(flags.getFlags());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<KeyFlag> getKeyFlagsOf(String userId) {
|
||||||
|
if (!isUserIdValid(userId)) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
PGPSignature userIdCertification = mostRecentUserIdSignatures.get(userId);
|
||||||
|
if (userIdCertification == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyFlags keyFlags = SignatureSubpacketsUtil.getKeyFlags(userIdCertification);
|
||||||
|
if (keyFlags != null) {
|
||||||
|
return KeyFlag.fromBitmask(keyFlags.getFlags());
|
||||||
|
}
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the algorithm of the primary key.
|
* Return the algorithm of the primary key.
|
||||||
*
|
*
|
||||||
|
@ -232,17 +374,26 @@ public class KeyRingInfo {
|
||||||
* @return last modification date.
|
* @return last modification date.
|
||||||
*/
|
*/
|
||||||
public Date getLastModified() {
|
public Date getLastModified() {
|
||||||
Iterator<PGPSignature> signatures = getPublicKey().getSignatures();
|
PGPSignature mostRecent = getMostRecentSignature();
|
||||||
long last = 0L;
|
return mostRecent.getCreationTime();
|
||||||
while (signatures.hasNext()) {
|
|
||||||
PGPSignature signature = signatures.next();
|
|
||||||
if (getKeyId() != signature.getKeyID()) {
|
|
||||||
// Throw away signatures made from others
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
last = Math.max(last, signature.getCreationTime().getTime());
|
|
||||||
|
private PGPSignature getMostRecentSignature() {
|
||||||
|
Set<PGPSignature> allSignatures = new HashSet<>();
|
||||||
|
if (mostRecentSelfSignature != null) allSignatures.add(mostRecentSelfSignature);
|
||||||
|
if (revocationSelfSignature != null) allSignatures.add(revocationSelfSignature);
|
||||||
|
allSignatures.addAll(mostRecentUserIdSignatures.values());
|
||||||
|
allSignatures.addAll(mostRecentUserIdRevocations.values());
|
||||||
|
allSignatures.addAll(mostRecentSubkeyBindings.values());
|
||||||
|
allSignatures.addAll(mostRecentSubkeyRevocations.values());
|
||||||
|
|
||||||
|
PGPSignature mostRecent = null;
|
||||||
|
for (PGPSignature signature : allSignatures) {
|
||||||
|
if (mostRecent == null || signature.getCreationTime().after(mostRecent.getCreationTime())) {
|
||||||
|
mostRecent = signature;
|
||||||
}
|
}
|
||||||
return new Date(last);
|
}
|
||||||
|
return mostRecent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -251,16 +402,7 @@ public class KeyRingInfo {
|
||||||
* @return revocation date or null
|
* @return revocation date or null
|
||||||
*/
|
*/
|
||||||
public Date getRevocationDate() {
|
public Date getRevocationDate() {
|
||||||
Iterator<PGPSignature> revocations = getPublicKey().getSignaturesOfType(SignatureType.KEY_REVOCATION.getCode());
|
return revocationSelfSignature == null ? null : revocationSelfSignature.getCreationTime();
|
||||||
while (revocations.hasNext()) {
|
|
||||||
PGPSignature revocation = revocations.next();
|
|
||||||
if (getKeyId() != revocation.getKeyID()) {
|
|
||||||
// Throw away signatures made from others
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
return revocation.getCreationTime();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -268,16 +410,32 @@ public class KeyRingInfo {
|
||||||
*
|
*
|
||||||
* @return expiration date
|
* @return expiration date
|
||||||
*/
|
*/
|
||||||
public Date getExpirationDate() {
|
public Date getPrimaryKeyExpirationDate() {
|
||||||
return getExpirationDate(new OpenPgpV4Fingerprint(getPublicKey()));
|
Date lastExpiration = null;
|
||||||
|
if (mostRecentSelfSignature != null) {
|
||||||
|
lastExpiration = SignatureUtils.getKeyExpirationDate(getCreationDate(), mostRecentSelfSignature);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Date getExpirationDate(OpenPgpV4Fingerprint fingerprint) {
|
for (String userId : getValidUserIds()) {
|
||||||
long validSeconds = keys.getPublicKey(fingerprint.getKeyId()).getValidSeconds();
|
PGPSignature signature = getCurrentUserIdCertification(userId);
|
||||||
if (validSeconds == 0) {
|
Date expiration = SignatureUtils.getKeyExpirationDate(getCreationDate(), signature);
|
||||||
return null;
|
if (expiration != null && (lastExpiration == null || expiration.after(lastExpiration))) {
|
||||||
|
lastExpiration = expiration;
|
||||||
}
|
}
|
||||||
return new Date(getCreationDate().getTime() + (1000 * validSeconds));
|
}
|
||||||
|
return lastExpiration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getSubkeyExpirationDate(OpenPgpV4Fingerprint fingerprint) {
|
||||||
|
if (getPublicKey().getKeyID() == fingerprint.getKeyId()) {
|
||||||
|
return getPrimaryKeyExpirationDate();
|
||||||
|
}
|
||||||
|
|
||||||
|
PGPPublicKey subkey = getPublicKey(fingerprint.getKeyId());
|
||||||
|
if (subkey == null) {
|
||||||
|
throw new IllegalArgumentException("No subkey with fingerprint " + fingerprint + " found.");
|
||||||
|
}
|
||||||
|
return SignatureUtils.getKeyExpirationDate(subkey.getCreationTime(), mostRecentSubkeyBindings.get(fingerprint.getKeyId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -334,67 +492,4 @@ public class KeyRingInfo {
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<PGPSignature> getSelfSignaturesOnKey(long subkeyId) {
|
|
||||||
PGPPublicKey publicKey = KeyRingUtils.requirePublicKeyFrom(keys, subkeyId);
|
|
||||||
Iterator<PGPSignature> it = publicKey.getSignaturesForKeyID(keys.getPublicKey().getKeyID());
|
|
||||||
List<PGPSignature> signatures = iteratorToList(it);
|
|
||||||
sortByCreationTimeAscending(signatures);
|
|
||||||
return signatures;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PGPSignature getLatestValidSelfSignatureOnKey() throws PGPException {
|
|
||||||
return getLatestValidSelfSignatureOnKey(new OpenPgpV4Fingerprint(getPublicKey()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public PGPSignature getLatestValidSelfSignatureOnKey(OpenPgpV4Fingerprint fingerprint) throws PGPException {
|
|
||||||
return getLatestValidSelfSignatureOnKey(fingerprint.getKeyId());
|
|
||||||
}
|
|
||||||
|
|
||||||
public PGPSignature getLatestValidSelfSignatureOnKey(long subkeyId) throws PGPException {
|
|
||||||
PGPPublicKey publicKey = KeyRingUtils.requirePublicKeyFrom(keys, subkeyId);
|
|
||||||
List<PGPSignature> signatures = getSelfSignaturesOnKey(keys.getPublicKey().getKeyID());
|
|
||||||
return getLatestValidSignature(publicKey, signatures, keys);
|
|
||||||
}
|
|
||||||
|
|
||||||
public PGPSignature getLatestValidSignatureOnUserId(String userId) throws PGPException {
|
|
||||||
PGPPublicKey publicKey = KeyRingUtils.requirePrimaryPublicKeyFrom(keys);
|
|
||||||
Iterator<PGPSignature> iterator = publicKey.getSignaturesForID(userId);
|
|
||||||
List<PGPSignature> signatures = iteratorToList(iterator);
|
|
||||||
return getLatestValidSignature(publicKey, signatures, keys);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<PGPSignature> getBindingSignaturesOnKey(OpenPgpV4Fingerprint fingerprint) {
|
|
||||||
return getBindingSignaturesOnKey(fingerprint.getKeyId());
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<PGPSignature> getBindingSignaturesOnKey(long subkeyId) {
|
|
||||||
if (subkeyId == getKeyId()) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
PGPPublicKey publicKey = KeyRingUtils.requirePublicKeyFrom(keys, subkeyId);
|
|
||||||
return SignatureUtils.getBindingSignatures(publicKey, getKeyId());
|
|
||||||
}
|
|
||||||
|
|
||||||
public PGPSignature getLatestValidBindingSignatureOnKey(long subKeyID) throws PGPException {
|
|
||||||
PGPPublicKey publicKey = KeyRingUtils.requirePublicKeyFrom(keys, subKeyID);
|
|
||||||
List<PGPSignature> signatures = getBindingSignaturesOnKey(subKeyID);
|
|
||||||
return getLatestValidSignature(publicKey, signatures, keys);
|
|
||||||
}
|
|
||||||
|
|
||||||
public PGPSignature getLatestValidSelfOrBindingSignatureOnKey(OpenPgpV4Fingerprint fingerprint) throws PGPException {
|
|
||||||
return getLatestValidSelfOrBindingSignatureOnKey(fingerprint.getKeyId());
|
|
||||||
}
|
|
||||||
|
|
||||||
public PGPSignature getLatestValidSelfOrBindingSignatureOnKey(long subKeyId) throws PGPException {
|
|
||||||
PGPSignature self = getLatestValidSelfSignatureOnKey(subKeyId);
|
|
||||||
PGPSignature binding = getLatestValidBindingSignatureOnKey(subKeyId);
|
|
||||||
if (self == null) {
|
|
||||||
return binding;
|
|
||||||
}
|
|
||||||
if (binding == null) {
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
return self.getCreationTime().after(binding.getCreationTime()) ? self : binding;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,9 +63,9 @@ import org.pgpainless.key.protection.UnprotectedKeysProtector;
|
||||||
import org.pgpainless.key.protection.passphrase_provider.SolitaryPassphraseProvider;
|
import org.pgpainless.key.protection.passphrase_provider.SolitaryPassphraseProvider;
|
||||||
import org.pgpainless.key.util.KeyRingUtils;
|
import org.pgpainless.key.util.KeyRingUtils;
|
||||||
import org.pgpainless.key.util.RevocationAttributes;
|
import org.pgpainless.key.util.RevocationAttributes;
|
||||||
import org.pgpainless.key.util.SignatureUtils;
|
import org.pgpainless.signature.SignatureUtils;
|
||||||
import org.pgpainless.util.Passphrase;
|
import org.pgpainless.util.Passphrase;
|
||||||
import org.pgpainless.util.SignatureSubpacketGeneratorUtil;
|
import org.pgpainless.signature.subpackets.SignatureSubpacketGeneratorUtil;
|
||||||
import org.pgpainless.util.selection.userid.SelectUserId;
|
import org.pgpainless.util.selection.userid.SelectUserId;
|
||||||
|
|
||||||
public class SecretKeyRingEditor implements SecretKeyRingEditorInterface {
|
public class SecretKeyRingEditor implements SecretKeyRingEditorInterface {
|
||||||
|
@ -563,12 +563,16 @@ public class SecretKeyRingEditor implements SecretKeyRingEditorInterface {
|
||||||
signatureGenerator.setHashedSubpackets(subPackets);
|
signatureGenerator.setHashedSubpackets(subPackets);
|
||||||
|
|
||||||
PGPPrivateKey privateKey = primaryKey.extractPrivateKey(protector.getDecryptor(primaryKey.getKeyID()));
|
PGPPrivateKey privateKey = primaryKey.extractPrivateKey(protector.getDecryptor(primaryKey.getKeyID()));
|
||||||
SignatureType type = revokeeSubKey.isMasterKey() ? SignatureType.KEY_REVOCATION : SignatureType.SUBKEY_REVOCATION;
|
|
||||||
signatureGenerator.init(type.getCode(), privateKey);
|
|
||||||
|
|
||||||
// Generate revocation
|
PGPSignature revocation;
|
||||||
PGPSignature subKeyRevocation = signatureGenerator.generateCertification(primaryKey.getPublicKey(), revokeeSubKey);
|
if (revokeeSubKey.isMasterKey()) {
|
||||||
return subKeyRevocation;
|
signatureGenerator.init(SignatureType.KEY_REVOCATION.getCode(), privateKey);
|
||||||
|
revocation = signatureGenerator.generateCertification(revokeeSubKey);
|
||||||
|
} else {
|
||||||
|
signatureGenerator.init(SignatureType.SUBKEY_REVOCATION.getCode(), privateKey);
|
||||||
|
revocation = signatureGenerator.generateCertification(primaryKey.getPublicKey(), revokeeSubKey);
|
||||||
|
}
|
||||||
|
return revocation;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -15,6 +15,9 @@
|
||||||
*/
|
*/
|
||||||
package org.pgpainless.key.util;
|
package org.pgpainless.key.util;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
public final class RevocationAttributes {
|
public final class RevocationAttributes {
|
||||||
|
|
||||||
public enum Reason {
|
public enum Reason {
|
||||||
|
@ -25,6 +28,26 @@ public final class RevocationAttributes {
|
||||||
USER_ID_NO_LONGER_VALID((byte) 32),
|
USER_ID_NO_LONGER_VALID((byte) 32),
|
||||||
;
|
;
|
||||||
|
|
||||||
|
private static final Map<Byte, Reason> MAP = new ConcurrentHashMap<>();
|
||||||
|
static {
|
||||||
|
for (Reason r : Reason.values()) {
|
||||||
|
MAP.put(r.reasonCode, r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Reason fromCode(byte code) {
|
||||||
|
Reason reason = MAP.get(code);
|
||||||
|
if (reason == null) {
|
||||||
|
throw new IllegalArgumentException("Invalid revocation reason: " + code);
|
||||||
|
}
|
||||||
|
return reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isHardRevocation(byte code) {
|
||||||
|
Reason reason = MAP.get(code);
|
||||||
|
return reason != KEY_SUPERSEDED && reason != KEY_RETIRED && reason != USER_ID_NO_LONGER_VALID;
|
||||||
|
}
|
||||||
|
|
||||||
private final byte reasonCode;
|
private final byte reasonCode;
|
||||||
|
|
||||||
Reason(byte reasonCode) {
|
Reason(byte reasonCode) {
|
||||||
|
@ -35,7 +58,6 @@ public final class RevocationAttributes {
|
||||||
return reasonCode;
|
return reasonCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return code() + " - " + name();
|
return code() + " - " + name();
|
||||||
|
|
|
@ -17,10 +17,12 @@ package org.pgpainless.policy;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.pgpainless.algorithm.HashAlgorithm;
|
import org.pgpainless.algorithm.HashAlgorithm;
|
||||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||||
|
import org.pgpainless.util.NotationRegistry;
|
||||||
|
|
||||||
public final class Policy {
|
public final class Policy {
|
||||||
|
|
||||||
|
@ -32,6 +34,8 @@ public final class Policy {
|
||||||
HashAlgorithmPolicy.defaultRevocationSignatureHashAlgorithmPolicy();
|
HashAlgorithmPolicy.defaultRevocationSignatureHashAlgorithmPolicy();
|
||||||
private SymmetricKeyAlgorithmPolicy symmetricKeyAlgorithmPolicy =
|
private SymmetricKeyAlgorithmPolicy symmetricKeyAlgorithmPolicy =
|
||||||
SymmetricKeyAlgorithmPolicy.defaultSymmetricKeyAlgorithmPolicy();
|
SymmetricKeyAlgorithmPolicy.defaultSymmetricKeyAlgorithmPolicy();
|
||||||
|
private ValidationDateProvider signatureValidationDateProvider = getDefaultSignatureValidationDateProvider();
|
||||||
|
private final NotationRegistry notationRegistry = new NotationRegistry();
|
||||||
|
|
||||||
private Policy() {
|
private Policy() {
|
||||||
}
|
}
|
||||||
|
@ -158,4 +162,36 @@ public final class Policy {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Date getSignatureValidationDate() {
|
||||||
|
return getSignatureValidationDateProvider().getValidationDate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValidationDateProvider getDefaultSignatureValidationDateProvider() {
|
||||||
|
return new ValidationDateProvider() {
|
||||||
|
@Override
|
||||||
|
public Date getValidationDate() {
|
||||||
|
return new Date(); // now
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValidationDateProvider getSignatureValidationDateProvider() {
|
||||||
|
return signatureValidationDateProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValidationDateProvider(ValidationDateProvider validationDateProvider) {
|
||||||
|
if (validationDateProvider == null) {
|
||||||
|
throw new NullPointerException("ValidationDateProvider MUST NOT be null.");
|
||||||
|
}
|
||||||
|
this.signatureValidationDateProvider = validationDateProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ValidationDateProvider {
|
||||||
|
Date getValidationDate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public NotationRegistry getNotationRegistry() {
|
||||||
|
return notationRegistry;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,19 +13,23 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.pgpainless.decryption_verification;
|
package org.pgpainless.signature;
|
||||||
|
|
||||||
|
import org.bouncycastle.openpgp.PGPKeyRing;
|
||||||
import org.bouncycastle.openpgp.PGPSignature;
|
import org.bouncycastle.openpgp.PGPSignature;
|
||||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||||
|
import org.pgpainless.key.SubkeyIdentifier;
|
||||||
|
|
||||||
public class DetachedSignature {
|
public class DetachedSignature {
|
||||||
private final PGPSignature signature;
|
private final PGPSignature signature;
|
||||||
private final OpenPgpV4Fingerprint fingerprint;
|
private final PGPKeyRing signingKeyRing;
|
||||||
|
private final SubkeyIdentifier signingKeyIdentifier;
|
||||||
private boolean verified;
|
private boolean verified;
|
||||||
|
|
||||||
public DetachedSignature(PGPSignature signature, OpenPgpV4Fingerprint fingerprint) {
|
public DetachedSignature(PGPSignature signature, PGPKeyRing signingKeyRing, SubkeyIdentifier signingKeyIdentifier) {
|
||||||
this.signature = signature;
|
this.signature = signature;
|
||||||
this.fingerprint = fingerprint;
|
this.signingKeyRing = signingKeyRing;
|
||||||
|
this.signingKeyIdentifier = signingKeyIdentifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setVerified(boolean verified) {
|
public void setVerified(boolean verified) {
|
||||||
|
@ -40,7 +44,16 @@ public class DetachedSignature {
|
||||||
return signature;
|
return signature;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SubkeyIdentifier getSigningKeyIdentifier() {
|
||||||
|
return signingKeyIdentifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PGPKeyRing getSigningKeyRing() {
|
||||||
|
return signingKeyRing;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public OpenPgpV4Fingerprint getFingerprint() {
|
public OpenPgpV4Fingerprint getFingerprint() {
|
||||||
return fingerprint;
|
return signingKeyIdentifier.getSubkeyFingerprint();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -13,22 +13,23 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.pgpainless.decryption_verification;
|
package org.pgpainless.signature;
|
||||||
|
|
||||||
import org.bouncycastle.openpgp.PGPException;
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
import org.bouncycastle.openpgp.PGPOnePassSignature;
|
import org.bouncycastle.openpgp.PGPOnePassSignature;
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||||
import org.bouncycastle.openpgp.PGPSignature;
|
import org.bouncycastle.openpgp.PGPSignature;
|
||||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||||
|
|
||||||
public class OnePassSignature {
|
public class OnePassSignature {
|
||||||
private final PGPOnePassSignature onePassSignature;
|
private final PGPOnePassSignature onePassSignature;
|
||||||
private final OpenPgpV4Fingerprint fingerprint;
|
private final PGPPublicKeyRing verificationKeys;
|
||||||
private PGPSignature signature;
|
private PGPSignature signature;
|
||||||
private boolean verified;
|
private boolean verified;
|
||||||
|
|
||||||
public OnePassSignature(PGPOnePassSignature onePassSignature, OpenPgpV4Fingerprint fingerprint) {
|
public OnePassSignature(PGPOnePassSignature onePassSignature, PGPPublicKeyRing verificationKeys) {
|
||||||
this.onePassSignature = onePassSignature;
|
this.onePassSignature = onePassSignature;
|
||||||
this.fingerprint = fingerprint;
|
this.verificationKeys = verificationKeys;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isVerified() {
|
public boolean isVerified() {
|
||||||
|
@ -40,7 +41,7 @@ public class OnePassSignature {
|
||||||
}
|
}
|
||||||
|
|
||||||
public OpenPgpV4Fingerprint getFingerprint() {
|
public OpenPgpV4Fingerprint getFingerprint() {
|
||||||
return fingerprint;
|
return new OpenPgpV4Fingerprint(verificationKeys.getPublicKey(onePassSignature.getKeyID()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean verify(PGPSignature signature) throws PGPException {
|
public boolean verify(PGPSignature signature) throws PGPException {
|
||||||
|
@ -54,4 +55,8 @@ public class OnePassSignature {
|
||||||
public PGPSignature getSignature() {
|
public PGPSignature getSignature() {
|
||||||
return signature;
|
return signature;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PGPPublicKeyRing getVerificationKeys() {
|
||||||
|
return verificationKeys;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,392 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 Paul Schaub.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.pgpainless.signature;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
|
import org.bouncycastle.openpgp.PGPKeyRing;
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||||
|
import org.bouncycastle.openpgp.PGPSignature;
|
||||||
|
import org.bouncycastle.openpgp.PGPSignatureList;
|
||||||
|
import org.pgpainless.algorithm.KeyFlag;
|
||||||
|
import org.pgpainless.algorithm.SignatureType;
|
||||||
|
import org.pgpainless.implementation.ImplementationFactory;
|
||||||
|
import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil;
|
||||||
|
|
||||||
|
public abstract class SelectSignatureFromKey {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(SelectSignatureFromKey.class.getName());
|
||||||
|
|
||||||
|
public static SelectSignatureFromKey isValidAt(Date validationDate) {
|
||||||
|
return new SelectSignatureFromKey() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(PGPSignature signature, PGPPublicKey key, PGPKeyRing keyRing) {
|
||||||
|
Date expirationDate = SignatureUtils.getSignatureExpirationDate(signature);
|
||||||
|
return !signature.getCreationTime().after(validationDate) && (expirationDate == null || expirationDate.after(validationDate));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract boolean accept(PGPSignature signature, PGPPublicKey key, PGPKeyRing keyRing);
|
||||||
|
|
||||||
|
public List<PGPSignature> select(List<PGPSignature> signatures, PGPPublicKey key, PGPKeyRing keyRing) {
|
||||||
|
List<PGPSignature> selected = new ArrayList<>();
|
||||||
|
for (PGPSignature signature : signatures) {
|
||||||
|
if (accept(signature, key, keyRing)) {
|
||||||
|
selected.add(signature);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SelectSignatureFromKey isValidSubkeyBindingSignature(PGPPublicKey primaryKey, PGPPublicKey subkey) {
|
||||||
|
return new SelectSignatureFromKey() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(PGPSignature signature, PGPPublicKey key, PGPKeyRing keyRing) {
|
||||||
|
|
||||||
|
if (!isOfType(SignatureType.SUBKEY_BINDING).accept(signature, key, keyRing)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (signature.getKeyID() != primaryKey.getKeyID()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean subkeyBindingSigValid;
|
||||||
|
try {
|
||||||
|
signature.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), primaryKey);
|
||||||
|
subkeyBindingSigValid = signature.verifyCertification(primaryKey, subkey);
|
||||||
|
} catch (PGPException e) {
|
||||||
|
LOGGER.log(Level.INFO, "Verification of subkey binding signature failed.", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!subkeyBindingSigValid) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<KeyFlag> flags = KeyFlag.fromBitmask(signature.getHashedSubPackets().getKeyFlags());
|
||||||
|
boolean isSigningKey = flags.contains(KeyFlag.SIGN_DATA) || flags.contains(KeyFlag.CERTIFY_OTHER);
|
||||||
|
|
||||||
|
if (isSigningKey && !hasValidPrimaryKeyBindingSignatureSubpacket(subkey, primaryKey)
|
||||||
|
.accept(signature, subkey, keyRing)) {
|
||||||
|
LOGGER.log(Level.INFO, "Subkey binding signature on signing key does not carry valid primary key binding signature.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SelectSignatureFromKey isValidPrimaryKeyBindingSignature(PGPPublicKey subkey, PGPPublicKey primaryKey) {
|
||||||
|
return new SelectSignatureFromKey() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(PGPSignature signature, PGPPublicKey key, PGPKeyRing keyRing) {
|
||||||
|
|
||||||
|
if (!isVersion4Signature().accept(signature, key, keyRing)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isOfType(SignatureType.PRIMARYKEY_BINDING).accept(signature, key, keyRing)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (signature.getKeyID() != subkey.getKeyID()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isSigNotExpired().accept(signature, primaryKey, keyRing)) {
|
||||||
|
LOGGER.log(Level.INFO, "Primary key binding signature expired.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
signature.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), subkey);
|
||||||
|
return signature.verifyCertification(primaryKey, subkey);
|
||||||
|
} catch (PGPException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SelectSignatureFromKey hasValidPrimaryKeyBindingSignatureSubpacket(PGPPublicKey subkey, PGPPublicKey primaryKey) {
|
||||||
|
return new SelectSignatureFromKey() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(PGPSignature signature, PGPPublicKey key, PGPKeyRing keyRing) {
|
||||||
|
try {
|
||||||
|
PGPSignatureList embeddedSignatures = SignatureSubpacketsUtil.getEmbeddedSignature(signature);
|
||||||
|
if (embeddedSignatures != null) {
|
||||||
|
for (PGPSignature embeddedSignature : embeddedSignatures) {
|
||||||
|
if (isValidPrimaryKeyBindingSignature(subkey, primaryKey).accept(embeddedSignature, subkey, keyRing)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (PGPException e) {
|
||||||
|
LOGGER.log(Level.WARNING, "Cannot parse embedded signatures:", e);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SelectSignatureFromKey isValidDirectKeySignature(PGPPublicKey signer, PGPPublicKey signee) {
|
||||||
|
return new SelectSignatureFromKey() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(PGPSignature signature, PGPPublicKey key, PGPKeyRing keyRing) {
|
||||||
|
if (!isVersion4Signature().accept(signature, key, keyRing)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isOfType(SignatureType.DIRECT_KEY).accept(signature, key, keyRing)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
signature.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), signer);
|
||||||
|
return signature.verifyCertification(signee);
|
||||||
|
} catch (PGPException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SelectSignatureFromKey isValidKeyRevocationSignature(PGPPublicKey key) {
|
||||||
|
return and(
|
||||||
|
isVersion4Signature(),
|
||||||
|
isOfType(SignatureType.KEY_REVOCATION),
|
||||||
|
isCreatedBy(key),
|
||||||
|
isWellFormed(),
|
||||||
|
doesNotPredateKeyCreationDate(key),
|
||||||
|
isVerifyingSignatureOnKey(key, key)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SelectSignatureFromKey isValidSubkeyRevocationSignature() {
|
||||||
|
return new SelectSignatureFromKey() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(PGPSignature signature, PGPPublicKey key, PGPKeyRing keyRing) {
|
||||||
|
return isValidSubkeyRevocationSignature(key, keyRing.getPublicKey())
|
||||||
|
.accept(signature, key, keyRing);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SelectSignatureFromKey isValidSubkeyRevocationSignature(PGPPublicKey subkey, PGPPublicKey primaryKey) {
|
||||||
|
return SelectSignatureFromKey.and(
|
||||||
|
isVersion4Signature(),
|
||||||
|
isOfType(SignatureType.SUBKEY_REVOCATION),
|
||||||
|
isCreatedBy(primaryKey),
|
||||||
|
isVerifyingSignatureOnKeys(primaryKey, subkey, primaryKey)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SelectSignatureFromKey isValidCertificationRevocationSignature(PGPPublicKey revoker, String userId) {
|
||||||
|
return and(
|
||||||
|
isVersion4Signature(),
|
||||||
|
isCreatedBy(revoker),
|
||||||
|
isOfType(SignatureType.CERTIFICATION_REVOCATION),
|
||||||
|
isValidSignatureOnUserId(userId, revoker)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SelectSignatureFromKey isValidSignatureOnUserId(String userId, PGPPublicKey signingKey) {
|
||||||
|
return new SelectSignatureFromKey() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(PGPSignature signature, PGPPublicKey key, PGPKeyRing keyRing) {
|
||||||
|
try {
|
||||||
|
signature.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), signingKey);
|
||||||
|
return signature.verifyCertification(userId, key);
|
||||||
|
} catch (PGPException e) {
|
||||||
|
LOGGER.log(Level.INFO, "Verification of signature on userID " + userId + " failed.", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SelectSignatureFromKey isVerifyingSignatureOnKey(PGPPublicKey target, PGPPublicKey signer) {
|
||||||
|
return new SelectSignatureFromKey() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(PGPSignature signature, PGPPublicKey key, PGPKeyRing keyRing) {
|
||||||
|
try {
|
||||||
|
signature.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), signer);
|
||||||
|
boolean valid = signature.verifyCertification(target);
|
||||||
|
return valid;
|
||||||
|
} catch (PGPException e) {
|
||||||
|
LOGGER.log(Level.INFO, "Signature verification failed.", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SelectSignatureFromKey isVerifyingSignatureOnKeys(PGPPublicKey primaryKey, PGPPublicKey subkey, PGPPublicKey signingKey) {
|
||||||
|
if (signingKey.getKeyID() != primaryKey.getKeyID() && signingKey.getKeyID() != subkey.getKeyID()) {
|
||||||
|
throw new IllegalArgumentException("Signing key MUST be either the primary or subkey.");
|
||||||
|
}
|
||||||
|
return new SelectSignatureFromKey() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(PGPSignature signature, PGPPublicKey key, PGPKeyRing keyRing) {
|
||||||
|
try {
|
||||||
|
signature.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), signingKey);
|
||||||
|
return signature.verifyCertification(primaryKey, subkey);
|
||||||
|
} catch (PGPException e) {
|
||||||
|
LOGGER.log(Level.INFO, "Verification of " + SignatureType.valueOf(signature.getSignatureType()) + " signature failed.", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SelectSignatureFromKey isCertification() {
|
||||||
|
return new SelectSignatureFromKey() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(PGPSignature signature, PGPPublicKey key, PGPKeyRing keyRing) {
|
||||||
|
return signature.isCertification();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SelectSignatureFromKey isWellFormed() {
|
||||||
|
return and(
|
||||||
|
hasCreationTimeSubpacket(),
|
||||||
|
doesNotPredateKeyCreationDate()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SelectSignatureFromKey isVersion4Signature() {
|
||||||
|
return isVersion(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SelectSignatureFromKey hasCreationTimeSubpacket() {
|
||||||
|
return new SelectSignatureFromKey() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(PGPSignature signature, PGPPublicKey key, PGPKeyRing keyRing) {
|
||||||
|
return signature.getHashedSubPackets().getSignatureCreationTime() != null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SelectSignatureFromKey isCreatedBy(PGPPublicKey publicKey) {
|
||||||
|
return isCreatedBy(publicKey.getKeyID());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SelectSignatureFromKey isCreatedBy(long keyId) {
|
||||||
|
return new SelectSignatureFromKey() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(PGPSignature signature, PGPPublicKey key, PGPKeyRing keyRing) {
|
||||||
|
return signature.getKeyID() == keyId;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SelectSignatureFromKey isSigNotExpired() {
|
||||||
|
return isSigNotExpired(new Date());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SelectSignatureFromKey isSigNotExpired(Date comparisonDate) {
|
||||||
|
return new SelectSignatureFromKey() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(PGPSignature signature, PGPPublicKey key, PGPKeyRing keyRing) {
|
||||||
|
return !SignatureUtils.isSignatureExpired(signature, comparisonDate);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SelectSignatureFromKey doesNotPredateKeyCreationDate() {
|
||||||
|
return new SelectSignatureFromKey() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(PGPSignature signature, PGPPublicKey key, PGPKeyRing keyRing) {
|
||||||
|
PGPPublicKey creator = keyRing.getPublicKey(signature.getKeyID());
|
||||||
|
if (creator == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return doesNotPredateKeyCreationDate(creator).accept(signature, key, keyRing);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SelectSignatureFromKey doesNotPredateKeyCreationDate(PGPPublicKey creator) {
|
||||||
|
return new SelectSignatureFromKey() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(PGPSignature signature, PGPPublicKey key, PGPKeyRing keyRing) {
|
||||||
|
return !signature.getCreationTime().before(creator.getCreationTime());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SelectSignatureFromKey isVersion(int version) {
|
||||||
|
return new SelectSignatureFromKey() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(PGPSignature signature, PGPPublicKey key, PGPKeyRing keyRing) {
|
||||||
|
return signature.getVersion() == version;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SelectSignatureFromKey isOfType(SignatureType signatureType) {
|
||||||
|
return new SelectSignatureFromKey() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(PGPSignature signature, PGPPublicKey key, PGPKeyRing keyRing) {
|
||||||
|
return signature.getSignatureType() == signatureType.getCode();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SelectSignatureFromKey and(SelectSignatureFromKey... selectors) {
|
||||||
|
return new SelectSignatureFromKey() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(PGPSignature signature, PGPPublicKey key, PGPKeyRing keyRing) {
|
||||||
|
for (SelectSignatureFromKey selector : selectors) {
|
||||||
|
if (!selector.accept(signature, key, keyRing)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SelectSignatureFromKey or(SelectSignatureFromKey... selectors) {
|
||||||
|
return new SelectSignatureFromKey() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(PGPSignature signature, PGPPublicKey key, PGPKeyRing keyRing) {
|
||||||
|
boolean accept = false;
|
||||||
|
for (SelectSignatureFromKey selector : selectors) {
|
||||||
|
accept |= selector.accept(signature, key, keyRing);
|
||||||
|
}
|
||||||
|
return accept;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SelectSignatureFromKey not(SelectSignatureFromKey selector) {
|
||||||
|
return new SelectSignatureFromKey() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(PGPSignature signature, PGPPublicKey key, PGPKeyRing keyRing) {
|
||||||
|
return !selector.accept(signature, key, keyRing);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,182 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 Paul Schaub.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.pgpainless.signature;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||||
|
import org.bouncycastle.openpgp.PGPSignature;
|
||||||
|
import org.pgpainless.algorithm.KeyFlag;
|
||||||
|
import org.pgpainless.algorithm.SignatureType;
|
||||||
|
import org.pgpainless.policy.Policy;
|
||||||
|
import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil;
|
||||||
|
|
||||||
|
public class SignatureChainValidator {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(SignatureChainValidator.class.getName());
|
||||||
|
|
||||||
|
public static boolean validateSigningKey(PGPSignature signature, PGPPublicKeyRing signingKeyRing, Policy policy, Date validationDate) throws SignatureValidationException {
|
||||||
|
|
||||||
|
Map<PGPSignature, Exception> rejections = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
PGPPublicKey signingSubkey = signingKeyRing.getPublicKey(signature.getKeyID());
|
||||||
|
if (signingSubkey == null) {
|
||||||
|
throw new SignatureValidationException("Provided key ring does not contain a subkey with id " + Long.toHexString(signature.getKeyID()));
|
||||||
|
}
|
||||||
|
|
||||||
|
PGPPublicKey primaryKey = signingKeyRing.getPublicKey();
|
||||||
|
|
||||||
|
List<PGPSignature> directKeySignatures = new ArrayList<>();
|
||||||
|
Iterator<PGPSignature> primaryKeyRevocationIterator = primaryKey.getSignaturesOfType(SignatureType.KEY_REVOCATION.getCode());
|
||||||
|
while (primaryKeyRevocationIterator.hasNext()) {
|
||||||
|
PGPSignature revocation = primaryKeyRevocationIterator.next();
|
||||||
|
try {
|
||||||
|
if (SignatureValidator.verifyKeyRevocationSignature(revocation, primaryKey, policy, signature.getCreationTime())) {
|
||||||
|
directKeySignatures.add(revocation);
|
||||||
|
}
|
||||||
|
} catch (SignatureValidationException e) {
|
||||||
|
rejections.put(revocation, e);
|
||||||
|
LOGGER.log(Level.FINE, "Rejecting key revocation signature.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterator<PGPSignature> keySignatures = primaryKey.getSignaturesOfType(SignatureType.DIRECT_KEY.getCode());
|
||||||
|
while (keySignatures.hasNext()) {
|
||||||
|
PGPSignature keySignature = keySignatures.next();
|
||||||
|
try {
|
||||||
|
if (SignatureValidator.verifyDirectKeySignature(keySignature, primaryKey, policy, signature.getCreationTime())) {
|
||||||
|
directKeySignatures.add(keySignature);
|
||||||
|
}
|
||||||
|
} catch (SignatureValidationException e) {
|
||||||
|
rejections.put(keySignature, e);
|
||||||
|
LOGGER.log(Level.FINE, "Rejecting key signature.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Collections.sort(directKeySignatures, new SignatureValidityComparator(SignatureCreationDateComparator.Order.new_to_old));
|
||||||
|
if (directKeySignatures.isEmpty()) {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if (directKeySignatures.get(0).getSignatureType() == SignatureType.KEY_REVOCATION.getCode()) {
|
||||||
|
throw new SignatureValidationException("Primary key has been revoked.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterator<String> userIds = primaryKey.getUserIDs();
|
||||||
|
Map<String, List<PGPSignature>> userIdSignatures = new ConcurrentHashMap<>();
|
||||||
|
while (userIds.hasNext()) {
|
||||||
|
List<PGPSignature> signaturesOnUserId = new ArrayList<>();
|
||||||
|
String userId = userIds.next();
|
||||||
|
Iterator<PGPSignature> userIdSigs = primaryKey.getSignaturesForID(userId);
|
||||||
|
while (userIdSigs.hasNext()) {
|
||||||
|
PGPSignature userIdSig = userIdSigs.next();
|
||||||
|
try {
|
||||||
|
if (SignatureValidator.verifySignatureOverUserId(userId, userIdSig, primaryKey, policy, signature.getCreationTime())) {
|
||||||
|
signaturesOnUserId.add(userIdSig);
|
||||||
|
}
|
||||||
|
} catch (SignatureValidationException e) {
|
||||||
|
rejections.put(userIdSig, e);
|
||||||
|
LOGGER.log(Level.INFO, "Rejecting user-id signature.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Collections.sort(signaturesOnUserId, new SignatureValidityComparator(SignatureCreationDateComparator.Order.new_to_old));
|
||||||
|
userIdSignatures.put(userId, signaturesOnUserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean userIdValid = false;
|
||||||
|
for (String userId : userIdSignatures.keySet()) {
|
||||||
|
if (!userIdSignatures.get(userId).isEmpty()) {
|
||||||
|
PGPSignature current = userIdSignatures.get(userId).get(0);
|
||||||
|
if (current.getSignatureType() == SignatureType.CERTIFICATION_REVOCATION.getCode()) {
|
||||||
|
LOGGER.log(Level.FINE, "User-ID '" + userId + "' is revoked.");
|
||||||
|
} else {
|
||||||
|
userIdValid = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!userIdValid) {
|
||||||
|
throw new SignatureValidationException("Key is not valid at this point.", rejections);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (signingSubkey != primaryKey) {
|
||||||
|
List<PGPSignature> subkeySigs = new ArrayList<>();
|
||||||
|
Iterator<PGPSignature> bindingRevocations = signingSubkey.getSignaturesOfType(SignatureType.SUBKEY_REVOCATION.getCode());
|
||||||
|
while (bindingRevocations.hasNext()) {
|
||||||
|
PGPSignature revocation = bindingRevocations.next();
|
||||||
|
try {
|
||||||
|
if (SignatureValidator.verifySubkeyBindingRevocation(revocation, primaryKey, signingSubkey, policy, signature.getCreationTime())) {
|
||||||
|
subkeySigs.add(revocation);
|
||||||
|
}
|
||||||
|
} catch (SignatureValidationException e) {
|
||||||
|
rejections.put(revocation, e);
|
||||||
|
LOGGER.log(Level.FINE, "Rejecting subkey revocation signature.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterator<PGPSignature> bindingSigs = signingSubkey.getSignaturesOfType(SignatureType.SUBKEY_BINDING.getCode());
|
||||||
|
while (bindingSigs.hasNext()) {
|
||||||
|
PGPSignature bindingSig = bindingSigs.next();
|
||||||
|
try {
|
||||||
|
if (SignatureValidator.verifySubkeyBindingSignature(bindingSig, primaryKey, signingSubkey, policy, signature.getCreationTime())) {
|
||||||
|
subkeySigs.add(bindingSig);
|
||||||
|
}
|
||||||
|
} catch (SignatureValidationException e) {
|
||||||
|
rejections.put(bindingSig, e);
|
||||||
|
LOGGER.log(Level.FINE, "Rejecting subkey binding signature.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Collections.sort(subkeySigs, new SignatureValidityComparator(SignatureCreationDateComparator.Order.new_to_old));
|
||||||
|
if (subkeySigs.isEmpty()) {
|
||||||
|
throw new SignatureValidationException("Subkey is not bound.", rejections);
|
||||||
|
}
|
||||||
|
|
||||||
|
PGPSignature currentSig = subkeySigs.get(0);
|
||||||
|
if (currentSig.getSignatureType() == SignatureType.SUBKEY_REVOCATION.getCode()) {
|
||||||
|
throw new SignatureValidationException("Subkey is revoked.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!KeyFlag.hasKeyFlag(SignatureSubpacketsUtil.getKeyFlags(currentSig).getFlags(), KeyFlag.SIGN_DATA)) {
|
||||||
|
throw new SignatureValidationException("Signature was made by key which is not capable of signing.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean validateSignatureChain(PGPSignature signature, InputStream signedData, PGPPublicKeyRing signingKeyRing, Policy policy, Date validationDate)
|
||||||
|
throws SignatureValidationException {
|
||||||
|
validateSigningKey(signature, signingKeyRing, policy, validationDate);
|
||||||
|
return SignatureValidator.verifyUninitializedSignature(signature, signedData, signingKeyRing.getPublicKey(signature.getKeyID()), policy, validationDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean validateSignature(PGPSignature signature, PGPPublicKeyRing verificationKeys, Policy policy) throws SignatureValidationException {
|
||||||
|
validateSigningKey(signature, verificationKeys, policy, signature.getCreationTime());
|
||||||
|
PGPPublicKey signingKey = verificationKeys.getPublicKey(signature.getKeyID());
|
||||||
|
SignatureValidator.verifyInitializedSignature(signature, signingKey, policy, signature.getCreationTime());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 Paul Schaub.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.pgpainless.signature;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
|
||||||
|
import org.bouncycastle.openpgp.PGPSignature;
|
||||||
|
|
||||||
|
public class SignatureCreationDateComparator implements Comparator<PGPSignature> {
|
||||||
|
|
||||||
|
public static final Order DEFAULT_ORDER = Order.old_to_new;
|
||||||
|
|
||||||
|
public enum Order {
|
||||||
|
old_to_new,
|
||||||
|
new_to_old
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Order order;
|
||||||
|
|
||||||
|
public SignatureCreationDateComparator() {
|
||||||
|
this(DEFAULT_ORDER);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SignatureCreationDateComparator(Order order) {
|
||||||
|
this.order = order;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compare(PGPSignature one, PGPSignature two) {
|
||||||
|
return order == Order.old_to_new
|
||||||
|
? one.getCreationTime().compareTo(two.getCreationTime())
|
||||||
|
: two.getCreationTime().compareTo(one.getCreationTime());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,314 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 Paul Schaub.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.pgpainless.signature;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.bouncycastle.bcpg.sig.RevocationReason;
|
||||||
|
import org.bouncycastle.bcpg.sig.SignatureCreationTime;
|
||||||
|
import org.bouncycastle.openpgp.PGPKeyRing;
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||||
|
import org.bouncycastle.openpgp.PGPSignature;
|
||||||
|
import org.pgpainless.algorithm.SignatureType;
|
||||||
|
import org.pgpainless.key.util.RevocationAttributes;
|
||||||
|
import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil;
|
||||||
|
import org.pgpainless.util.CollectionUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pick signatures from keys.
|
||||||
|
*
|
||||||
|
* The format of a V4 OpenPGP key is:
|
||||||
|
*
|
||||||
|
* Primary-Key
|
||||||
|
* [Revocation Self Signature]
|
||||||
|
* [Direct Key Signature...]
|
||||||
|
* User ID [Signature ...]
|
||||||
|
* [User ID [Signature ...] ...]
|
||||||
|
* [User Attribute [Signature ...] ...]
|
||||||
|
* [[Subkey [Binding-Signature-Revocation] Primary-Key-Binding-Signature] ...]
|
||||||
|
*/
|
||||||
|
public class SignaturePicker {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pick the most current (at the time of evaluation) key revocation signature.
|
||||||
|
* If there is a hard revocation signature, it is picked, regardless of expiration or creation time.
|
||||||
|
*
|
||||||
|
* @param keyRing key ring
|
||||||
|
* @return most recent, valid key revocation signature
|
||||||
|
*/
|
||||||
|
public static PGPSignature pickCurrentRevocationSelfSignature(PGPKeyRing keyRing, Date validationDate) {
|
||||||
|
PGPPublicKey primaryKey = keyRing.getPublicKey();
|
||||||
|
|
||||||
|
List<PGPSignature> signatures = getSortedSignaturesOfType(primaryKey, SignatureType.KEY_REVOCATION);
|
||||||
|
PGPSignature mostCurrentValidSig = null;
|
||||||
|
|
||||||
|
for (PGPSignature signature : signatures) {
|
||||||
|
if (!SelectSignatureFromKey.isWellFormed().accept(signature, primaryKey, keyRing)) {
|
||||||
|
// Signature is not well-formed. Reject
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SelectSignatureFromKey.isCreatedBy(keyRing.getPublicKey()).accept(signature, primaryKey, keyRing)) {
|
||||||
|
// Revocation signature was not created by primary key
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
RevocationReason reason = SignatureSubpacketsUtil.getRevocationReason(signature);
|
||||||
|
if (reason != null && !RevocationAttributes.Reason.isHardRevocation(reason.getRevocationReason())) {
|
||||||
|
// reason code states soft revocation
|
||||||
|
if (!SelectSignatureFromKey.isValidAt(validationDate).accept(signature, primaryKey, keyRing)) {
|
||||||
|
// Soft revocation is either expired or not yet valid
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SelectSignatureFromKey.isValidKeyRevocationSignature(primaryKey).accept(signature, primaryKey, keyRing)) {
|
||||||
|
// sig does not check out
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
mostCurrentValidSig = signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mostCurrentValidSig;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pick the current direct key self-signature on the primary key.
|
||||||
|
* @param keyRing key ring
|
||||||
|
* @param validationDate validation date
|
||||||
|
* @return direct-key self-signature
|
||||||
|
*/
|
||||||
|
public static PGPSignature pickCurrentDirectKeySelfSignature(PGPKeyRing keyRing, Date validationDate) {
|
||||||
|
PGPPublicKey primaryKey = keyRing.getPublicKey();
|
||||||
|
return pickCurrentDirectKeySignature(primaryKey, primaryKey, keyRing, validationDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pick the current direct-key signature made by the signing key on the signed key.
|
||||||
|
*
|
||||||
|
* @param signingKey key that created the signature
|
||||||
|
* @param signedKey key that carries the signature
|
||||||
|
* @param keyRing key ring
|
||||||
|
* @param validationDate validation date
|
||||||
|
* @return direct key sig
|
||||||
|
*/
|
||||||
|
public static PGPSignature pickCurrentDirectKeySignature(PGPPublicKey signingKey, PGPPublicKey signedKey, PGPKeyRing keyRing, Date validationDate) {
|
||||||
|
List<PGPSignature> directKeySignatures = getSortedSignaturesOfType(signedKey, SignatureType.DIRECT_KEY);
|
||||||
|
|
||||||
|
PGPSignature mostRecentDirectKeySigBySigningKey = null;
|
||||||
|
for (PGPSignature signature : directKeySignatures) {
|
||||||
|
if (!SelectSignatureFromKey.isWellFormed().accept(signature, signingKey, keyRing)) {
|
||||||
|
// signature is not well formed
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SelectSignatureFromKey.isValidAt(validationDate).accept(signature, signedKey, keyRing)) {
|
||||||
|
// Signature is either expired or not yet valid
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SelectSignatureFromKey.isValidDirectKeySignature(signingKey, signedKey).accept(signature, signedKey, keyRing)) {
|
||||||
|
// signature does not check out.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
mostRecentDirectKeySigBySigningKey = signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mostRecentDirectKeySigBySigningKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pick the most recent user-id revocation signature.
|
||||||
|
*
|
||||||
|
* @param keyRing key ring
|
||||||
|
* @param userId user-Id that gets revoked
|
||||||
|
* @param validationDate validation date
|
||||||
|
* @return revocation signature
|
||||||
|
*/
|
||||||
|
public static PGPSignature pickCurrentUserIdRevocationSignature(PGPKeyRing keyRing, String userId, Date validationDate) {
|
||||||
|
PGPPublicKey primaryKey = keyRing.getPublicKey();
|
||||||
|
|
||||||
|
Iterator<PGPSignature> certificationRevocations = primaryKey.getSignaturesOfType(SignatureType.CERTIFICATION_REVOCATION.getCode());
|
||||||
|
List<PGPSignature> signatures = CollectionUtils.iteratorToList(certificationRevocations);
|
||||||
|
Collections.sort(signatures, new SignatureCreationDateComparator());
|
||||||
|
|
||||||
|
PGPSignature mostRecentUserIdRevocation = null;
|
||||||
|
for (PGPSignature signature : signatures) {
|
||||||
|
if (!SelectSignatureFromKey.isWellFormed().accept(signature, primaryKey, keyRing)) {
|
||||||
|
// Sig is not well formed.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
RevocationReason reason = SignatureSubpacketsUtil.getRevocationReason(signature);
|
||||||
|
if (reason != null && !RevocationAttributes.Reason.isHardRevocation(reason.getRevocationReason())) {
|
||||||
|
// reason code states soft revocation
|
||||||
|
if (!SelectSignatureFromKey.isValidAt(validationDate).accept(signature, primaryKey, keyRing)) {
|
||||||
|
// Soft revocation is either expired or not yet valid
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SelectSignatureFromKey.isValidCertificationRevocationSignature(primaryKey, userId)
|
||||||
|
.accept(signature, primaryKey, keyRing)) {
|
||||||
|
// sig does not check out for userid
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
mostRecentUserIdRevocation = signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mostRecentUserIdRevocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pick the most current certification self-signature for the given user-id.
|
||||||
|
*
|
||||||
|
* @param keyRing keyring
|
||||||
|
* @param userId userid
|
||||||
|
* @param validationDate validation date
|
||||||
|
* @return user-id certification
|
||||||
|
*/
|
||||||
|
public static PGPSignature pickCurrentUserIdCertificationSignature(PGPKeyRing keyRing, String userId, Date validationDate) {
|
||||||
|
PGPPublicKey primaryKey = keyRing.getPublicKey();
|
||||||
|
|
||||||
|
Iterator<PGPSignature> userIdSigIterator = primaryKey.getSignaturesForID(userId);
|
||||||
|
List<PGPSignature> signatures = CollectionUtils.iteratorToList(userIdSigIterator);
|
||||||
|
Collections.sort(signatures, new SignatureCreationDateComparator());
|
||||||
|
|
||||||
|
PGPSignature mostRecentUserIdCertification = null;
|
||||||
|
for (PGPSignature signature : signatures) {
|
||||||
|
if (!SelectSignatureFromKey.isWellFormed().accept(signature, primaryKey, keyRing)) {
|
||||||
|
// Sig not well formed
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SelectSignatureFromKey.isValidAt(validationDate).accept(signature, primaryKey, keyRing)) {
|
||||||
|
// Sig is either expired or not valid yet
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SelectSignatureFromKey.isValidSignatureOnUserId(userId, primaryKey).accept(signature, primaryKey, keyRing)) {
|
||||||
|
// Sig does not check out
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
mostRecentUserIdCertification = signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mostRecentUserIdCertification;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the current subkey binding revocation signature for the given subkey.
|
||||||
|
*
|
||||||
|
* @param keyRing keyring
|
||||||
|
* @param subkey subkey
|
||||||
|
* @param validationDate validation date
|
||||||
|
* @return subkey revocation signature
|
||||||
|
*/
|
||||||
|
public static PGPSignature pickCurrentSubkeyBindingRevocationSignature(PGPKeyRing keyRing, PGPPublicKey subkey, Date validationDate) {
|
||||||
|
PGPPublicKey primaryKey = keyRing.getPublicKey();
|
||||||
|
if (primaryKey.getKeyID() == subkey.getKeyID()) {
|
||||||
|
throw new IllegalArgumentException("Primary key cannot have subkey binding revocations.");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<PGPSignature> subkeyRevocationSigs = getSortedSignaturesOfType(subkey, SignatureType.SUBKEY_BINDING);
|
||||||
|
PGPSignature mostRecentSubkeyRevocation = null;
|
||||||
|
|
||||||
|
for (PGPSignature signature : subkeyRevocationSigs) {
|
||||||
|
if (!SelectSignatureFromKey.isWellFormed().accept(signature, primaryKey, keyRing)) {
|
||||||
|
// Signature is not well formed
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
RevocationReason reason = SignatureSubpacketsUtil.getRevocationReason(signature);
|
||||||
|
if (reason != null && !RevocationAttributes.Reason.isHardRevocation(reason.getRevocationReason())) {
|
||||||
|
// reason code states soft revocation
|
||||||
|
if (!SelectSignatureFromKey.isValidAt(validationDate).accept(signature, primaryKey, keyRing)) {
|
||||||
|
// Soft revocation is either expired or not yet valid
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SelectSignatureFromKey.isValidSubkeyRevocationSignature().accept(signature, subkey, keyRing)) {
|
||||||
|
// Signature does not check out
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
mostRecentSubkeyRevocation = signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mostRecentSubkeyRevocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the (at the time of validation) most recent, valid subkey binding signature
|
||||||
|
* made by the primary key of the key ring on the subkey.
|
||||||
|
*
|
||||||
|
* @param keyRing key ring
|
||||||
|
* @param subkey subkey
|
||||||
|
* @param validationDate date of validation
|
||||||
|
* @return most recent valid subkey binding signature
|
||||||
|
*/
|
||||||
|
public static PGPSignature pickCurrentSubkeyBindingSignature(PGPKeyRing keyRing, PGPPublicKey subkey, Date validationDate) {
|
||||||
|
PGPPublicKey primaryKey = keyRing.getPublicKey();
|
||||||
|
if (primaryKey.getKeyID() == subkey.getKeyID()) {
|
||||||
|
throw new IllegalArgumentException("Primary key cannot have subkey binding signature.");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<PGPSignature> subkeyBindingSigs = getSortedSignaturesOfType(subkey, SignatureType.SUBKEY_BINDING);
|
||||||
|
PGPSignature mostCurrentValidSig = null;
|
||||||
|
|
||||||
|
for (PGPSignature signature : subkeyBindingSigs) {
|
||||||
|
// has hashed creation time, does not predate signing key creation date
|
||||||
|
if (!SelectSignatureFromKey.isWellFormed().accept(signature, primaryKey, keyRing)) {
|
||||||
|
// Signature is not well-formed. Reject.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
SignatureCreationTime creationTime = SignatureSubpacketsUtil.getSignatureCreationTime(signature);
|
||||||
|
if (creationTime.getTime().after(validationDate)) {
|
||||||
|
// signature is not yet valid
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SignatureUtils.isSignatureExpired(signature, validationDate)) {
|
||||||
|
// Signature is expired
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SelectSignatureFromKey.isValidSubkeyBindingSignature(primaryKey, subkey)
|
||||||
|
.accept(signature, subkey, keyRing)) {
|
||||||
|
// Invalid subkey binding signature
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
mostCurrentValidSig = signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mostCurrentValidSig;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<PGPSignature> getSortedSignaturesOfType(PGPPublicKey key, SignatureType type) {
|
||||||
|
Iterator<PGPSignature> signaturesOfType = key.getSignaturesOfType(type.getCode());
|
||||||
|
List<PGPSignature> signatureList = CollectionUtils.iteratorToList(signaturesOfType);
|
||||||
|
Collections.sort(signatureList, new SignatureCreationDateComparator());
|
||||||
|
return signatureList;
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,7 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.pgpainless.key.util;
|
package org.pgpainless.signature;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@ -22,6 +22,9 @@ import java.util.Date;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.bouncycastle.bcpg.sig.KeyExpirationTime;
|
||||||
|
import org.bouncycastle.bcpg.sig.RevocationReason;
|
||||||
|
import org.bouncycastle.bcpg.sig.SignatureExpirationTime;
|
||||||
import org.bouncycastle.openpgp.PGPException;
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
import org.bouncycastle.openpgp.PGPKeyRing;
|
import org.bouncycastle.openpgp.PGPKeyRing;
|
||||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||||
|
@ -33,6 +36,10 @@ import org.pgpainless.PGPainless;
|
||||||
import org.pgpainless.algorithm.HashAlgorithm;
|
import org.pgpainless.algorithm.HashAlgorithm;
|
||||||
import org.pgpainless.algorithm.SignatureType;
|
import org.pgpainless.algorithm.SignatureType;
|
||||||
import org.pgpainless.implementation.ImplementationFactory;
|
import org.pgpainless.implementation.ImplementationFactory;
|
||||||
|
import org.pgpainless.key.util.KeyRingUtils;
|
||||||
|
import org.pgpainless.key.util.OpenPgpKeyAttributeUtil;
|
||||||
|
import org.pgpainless.key.util.RevocationAttributes;
|
||||||
|
import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil;
|
||||||
|
|
||||||
public class SignatureUtils {
|
public class SignatureUtils {
|
||||||
|
|
||||||
|
@ -150,14 +157,36 @@ public class SignatureUtils {
|
||||||
return signature.verifyCertification(userId, publicKey);
|
return signature.verifyCertification(userId, publicKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Date getKeyExpirationDate(Date keyCreationDate, PGPSignature signature) {
|
||||||
|
KeyExpirationTime keyExpirationTime = SignatureSubpacketsUtil.getKeyExpirationTime(signature);
|
||||||
|
long expiresInSecs = keyExpirationTime == null ? 0 : keyExpirationTime.getTime();
|
||||||
|
return datePlusSeconds(keyCreationDate, expiresInSecs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Date getSignatureExpirationDate(PGPSignature signature) {
|
||||||
|
Date creationDate = signature.getCreationTime();
|
||||||
|
SignatureExpirationTime signatureExpirationTime = SignatureSubpacketsUtil.getSignatureExpirationTime(signature);
|
||||||
|
long expiresInSecs = signatureExpirationTime == null ? 0 : signatureExpirationTime.getTime();
|
||||||
|
return datePlusSeconds(creationDate, expiresInSecs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Date datePlusSeconds(Date date, long seconds) {
|
||||||
|
if (seconds == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new Date(date.getTime() + 1000 * seconds);
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean isSignatureExpired(PGPSignature signature) {
|
public static boolean isSignatureExpired(PGPSignature signature) {
|
||||||
long expiration = signature.getHashedSubPackets().getSignatureExpirationTime();
|
return isSignatureExpired(signature, new Date());
|
||||||
if (expiration == 0) {
|
}
|
||||||
|
|
||||||
|
public static boolean isSignatureExpired(PGPSignature signature, Date comparisonDate) {
|
||||||
|
Date expirationDate = getSignatureExpirationDate(signature);
|
||||||
|
if (expirationDate == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Date now = new Date();
|
return comparisonDate.after(expirationDate);
|
||||||
Date creation = signature.getCreationTime();
|
|
||||||
return now.after(new Date(creation.getTime() + 1000 * expiration));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void sortByCreationTimeAscending(List<PGPSignature> signatures) {
|
public static void sortByCreationTimeAscending(List<PGPSignature> signatures) {
|
||||||
|
@ -221,10 +250,38 @@ public class SignatureUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isUserIdValid(PGPPublicKey publicKey, String userId) throws PGPException {
|
public static boolean isUserIdValid(PGPPublicKey publicKey, String userId) throws PGPException {
|
||||||
|
return isUserIdValid(publicKey, userId, new Date());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isUserIdValid(PGPPublicKey publicKey, String userId, Date validationDate) throws PGPException {
|
||||||
PGPSignature latestSelfSig = getLatestSelfSignatureForUserId(publicKey, userId);
|
PGPSignature latestSelfSig = getLatestSelfSignatureForUserId(publicKey, userId);
|
||||||
if (latestSelfSig == null) {
|
if (latestSelfSig == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (latestSelfSig.getCreationTime().after(validationDate)) {
|
||||||
|
// Signature creation date lays in the future.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (isSignatureExpired(latestSelfSig, validationDate)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return latestSelfSig.getSignatureType() != SignatureType.CERTIFICATION_REVOCATION.getCode();
|
return latestSelfSig.getSignatureType() != SignatureType.CERTIFICATION_REVOCATION.getCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isHardRevocation(PGPSignature signature) {
|
||||||
|
|
||||||
|
SignatureType type = SignatureType.valueOf(signature.getSignatureType());
|
||||||
|
if (type != SignatureType.KEY_REVOCATION && type != SignatureType.SUBKEY_REVOCATION && type != SignatureType.CERTIFICATION_REVOCATION) {
|
||||||
|
// Not a revocation
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RevocationReason reasonSubpacket = SignatureSubpacketsUtil.getRevocationReason(signature);
|
||||||
|
if (reasonSubpacket == null) {
|
||||||
|
// no reason -> hard revocation
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return RevocationAttributes.Reason.isHardRevocation(reasonSubpacket.getRevocationReason());
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 Paul Schaub.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.pgpainless.signature;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.bouncycastle.openpgp.PGPSignature;
|
||||||
|
import org.pgpainless.algorithm.SignatureType;
|
||||||
|
|
||||||
|
public class SignatureValidationException extends Exception {
|
||||||
|
|
||||||
|
public SignatureValidationException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SignatureValidationException(String message, Throwable underlying) {
|
||||||
|
super(message, underlying);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SignatureValidationException(String message, Map<PGPSignature, Exception> rejections) {
|
||||||
|
super(message + ": " + exceptionMapToString(rejections));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String exceptionMapToString(Map<PGPSignature, Exception> rejections) {
|
||||||
|
String out = "";
|
||||||
|
out += rejections.size() + " Rejected signatures:\n";
|
||||||
|
for (PGPSignature signature : rejections.keySet()) {
|
||||||
|
out += SignatureType.valueOf(signature.getSignatureType()) + " " + signature.getCreationTime() + ": " + rejections.get(signature).getMessage() + "\n";
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,522 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 Paul Schaub.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.pgpainless.signature;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import org.bouncycastle.bcpg.sig.NotationData;
|
||||||
|
import org.bouncycastle.bcpg.sig.SignatureCreationTime;
|
||||||
|
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.bouncycastle.openpgp.PGPUserAttributeSubpacketVector;
|
||||||
|
import org.pgpainless.algorithm.HashAlgorithm;
|
||||||
|
import org.pgpainless.algorithm.PublicKeyAlgorithm;
|
||||||
|
import org.pgpainless.algorithm.SignatureSubpacket;
|
||||||
|
import org.pgpainless.algorithm.SignatureType;
|
||||||
|
import org.pgpainless.implementation.ImplementationFactory;
|
||||||
|
import org.pgpainless.policy.Policy;
|
||||||
|
import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil;
|
||||||
|
import org.pgpainless.util.NotationRegistry;
|
||||||
|
|
||||||
|
public abstract class SignatureValidator {
|
||||||
|
|
||||||
|
public abstract void verify(PGPSignature signature) throws SignatureValidationException;
|
||||||
|
|
||||||
|
public static boolean verifyUninitializedSignature(PGPSignature signature, InputStream signedData, PGPPublicKey signingKey, Policy policy, Date validationDate) throws SignatureValidationException {
|
||||||
|
initializeSignatureAndUpdateWithSignedData(signature, signedData, signingKey);
|
||||||
|
return verifyInitializedSignature(signature, signingKey, policy, validationDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void initializeSignatureAndUpdateWithSignedData(PGPSignature signature, InputStream signedData, PGPPublicKey signingKey)
|
||||||
|
throws SignatureValidationException {
|
||||||
|
try {
|
||||||
|
signature.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), signingKey);
|
||||||
|
int read;
|
||||||
|
while ((read = signedData.read()) != -1) {
|
||||||
|
signature.update((byte) read);
|
||||||
|
}
|
||||||
|
} catch (PGPException e) {
|
||||||
|
throw new SignatureValidationException("Cannot init signature.", e);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new SignatureValidationException("Cannot update signature.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean verifyInitializedSignature(PGPSignature signature, PGPPublicKey signingKey, Policy policy, Date validationDate)
|
||||||
|
throws SignatureValidationException {
|
||||||
|
signatureStructureIsAcceptable(signingKey, policy).verify(signature);
|
||||||
|
signatureIsEffective(validationDate).verify(signature);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!signature.verify()) {
|
||||||
|
throw new SignatureValidationException("Signature is not correct.");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (PGPException e) {
|
||||||
|
throw new SignatureValidationException("Could not verify signature correctness.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean verifySignatureOverUserId(String userId, PGPSignature signature, PGPPublicKey primaryKey, Policy policy, Date validationDate)
|
||||||
|
throws SignatureValidationException {
|
||||||
|
return verifySignatureOverUserId(userId, signature, primaryKey, primaryKey, policy, validationDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean verifySignatureOverUserId(String userId, PGPSignature signature, PGPPublicKey signingKey, PGPPublicKey keyWithUserId, Policy policy, Date validationDate)
|
||||||
|
throws SignatureValidationException {
|
||||||
|
SignatureType type = SignatureType.valueOf(signature.getSignatureType());
|
||||||
|
switch (type) {
|
||||||
|
case GENERIC_CERTIFICATION:
|
||||||
|
case NO_CERTIFICATION:
|
||||||
|
case CASUAL_CERTIFICATION:
|
||||||
|
case POSITIVE_CERTIFICATION:
|
||||||
|
return verifyUserIdCertification(userId, signature, signingKey, keyWithUserId, policy, validationDate);
|
||||||
|
case CERTIFICATION_REVOCATION:
|
||||||
|
return verifyUserIdRevocation(userId, signature, signingKey, keyWithUserId, policy, validationDate);
|
||||||
|
default:
|
||||||
|
throw new SignatureValidationException("Signature is not a valid user-id certification/revocation signature: " + type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean verifyUserIdCertification(String userId, PGPSignature signature, PGPPublicKey primaryKey, Policy policy, Date validationDate)
|
||||||
|
throws SignatureValidationException {
|
||||||
|
return verifyUserIdCertification(userId, signature, primaryKey, primaryKey, policy, validationDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean verifyUserIdCertification(String userId, PGPSignature signature, PGPPublicKey signingKey, PGPPublicKey keyWithUserId, Policy policy, Date validationDate)
|
||||||
|
throws SignatureValidationException {
|
||||||
|
|
||||||
|
signatureIsCertification().verify(signature);
|
||||||
|
signatureStructureIsAcceptable(signingKey, policy).verify(signature);
|
||||||
|
signatureIsEffective(validationDate).verify(signature);
|
||||||
|
correctSignatureOverUserId(userId, keyWithUserId, signingKey).verify(signature);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
public static boolean verifyUserIdRevocation(String userId, PGPSignature signature, PGPPublicKey primaryKey, Policy policy, Date validationDate)
|
||||||
|
throws SignatureValidationException {
|
||||||
|
return verifyUserIdRevocation(userId, signature, primaryKey, primaryKey, policy, validationDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean verifyUserIdRevocation(String userId, PGPSignature signature, PGPPublicKey signingKey, PGPPublicKey keyWithUserId, Policy policy, Date validationDate)
|
||||||
|
throws SignatureValidationException {
|
||||||
|
signatureIsOfType(SignatureType.CERTIFICATION_REVOCATION).verify(signature);
|
||||||
|
signatureStructureIsAcceptable(signingKey, policy).verify(signature);
|
||||||
|
signatureIsEffective(validationDate).verify(signature);
|
||||||
|
correctSignatureOverUserId(userId, keyWithUserId, signingKey).verify(signature);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean verifyUserAttributesCertification(PGPUserAttributeSubpacketVector userAttributes,
|
||||||
|
PGPSignature signature, PGPPublicKey primaryKey,
|
||||||
|
Policy policy, Date validationDate)
|
||||||
|
throws SignatureValidationException {
|
||||||
|
return verifyUserAttributesCertification(userAttributes, signature, primaryKey, primaryKey, policy, validationDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean verifyUserAttributesCertification(PGPUserAttributeSubpacketVector userAttributes,
|
||||||
|
PGPSignature signature, PGPPublicKey signingKey,
|
||||||
|
PGPPublicKey keyWithUserAttributes, Policy policy,
|
||||||
|
Date validationDate)
|
||||||
|
throws SignatureValidationException {
|
||||||
|
signatureIsCertification().verify(signature);
|
||||||
|
signatureStructureIsAcceptable(signingKey, policy).verify(signature);
|
||||||
|
signatureIsEffective(validationDate).verify(signature);
|
||||||
|
correctSignatureOverUserAttributes(userAttributes, keyWithUserAttributes, signingKey).verify(signature);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean verifyUserAttributesRevocation(PGPUserAttributeSubpacketVector userAttributes,
|
||||||
|
PGPSignature signature, PGPPublicKey primaryKey,
|
||||||
|
Policy policy, Date validationDate)
|
||||||
|
throws SignatureValidationException {
|
||||||
|
return verifyUserAttributesRevocation(userAttributes, signature, primaryKey, primaryKey, policy, validationDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean verifyUserAttributesRevocation(PGPUserAttributeSubpacketVector userAttributes,
|
||||||
|
PGPSignature signature, PGPPublicKey signingKey,
|
||||||
|
PGPPublicKey keyWithUserAttributes, Policy policy,
|
||||||
|
Date validationDate)
|
||||||
|
throws SignatureValidationException {
|
||||||
|
signatureIsOfType(SignatureType.CERTIFICATION_REVOCATION).verify(signature);
|
||||||
|
signatureStructureIsAcceptable(signingKey, policy).verify(signature);
|
||||||
|
signatureIsEffective(validationDate).verify(signature);
|
||||||
|
correctSignatureOverUserAttributes(userAttributes, keyWithUserAttributes, signingKey).verify(signature);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean verifySubkeyBindingSignature(PGPSignature signature, PGPPublicKey primaryKey, PGPPublicKey subkey, Policy policy, Date validationDate)
|
||||||
|
throws SignatureValidationException {
|
||||||
|
signatureIsOfType(SignatureType.SUBKEY_BINDING).verify(signature);
|
||||||
|
signatureStructureIsAcceptable(primaryKey, policy).verify(signature);
|
||||||
|
signatureIsEffective(validationDate).verify(signature);
|
||||||
|
hasValidPrimaryKeyBindingSignatureIfRequired(primaryKey, subkey, policy, validationDate).verify(signature);
|
||||||
|
correctSignatureOverKey(primaryKey, subkey).verify(signature);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean verifySubkeyBindingRevocation(PGPSignature signature, PGPPublicKey primaryKey, PGPPublicKey subkey, Policy policy, Date validationDate) throws SignatureValidationException {
|
||||||
|
signatureIsOfType(SignatureType.SUBKEY_REVOCATION).verify(signature);
|
||||||
|
signatureStructureIsAcceptable(primaryKey, policy).verify(signature);
|
||||||
|
signatureIsEffective(validationDate).verify(signature);
|
||||||
|
correctSignatureOverKey(primaryKey, subkey).verify(signature);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean verifyDirectKeySignature(PGPSignature signature, PGPPublicKey primaryKey, Policy policy, Date validationDate)
|
||||||
|
throws SignatureValidationException {
|
||||||
|
return verifyDirectKeySignature(signature, primaryKey, primaryKey, policy, validationDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean verifyDirectKeySignature(PGPSignature signature, PGPPublicKey signingKey, PGPPublicKey signedKey, Policy policy, Date validationDate)
|
||||||
|
throws SignatureValidationException {
|
||||||
|
signatureIsOfType(SignatureType.DIRECT_KEY).verify(signature);
|
||||||
|
signatureStructureIsAcceptable(signingKey, policy).verify(signature);
|
||||||
|
signatureIsEffective(validationDate).verify(signature);
|
||||||
|
correctSignatureOverKey(signingKey, signedKey).verify(signature);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean verifyKeyRevocationSignature(PGPSignature signature, PGPPublicKey primaryKey, Policy policy, Date validationDate)
|
||||||
|
throws SignatureValidationException {
|
||||||
|
signatureIsOfType(SignatureType.KEY_REVOCATION).verify(signature);
|
||||||
|
signatureStructureIsAcceptable(primaryKey, policy).verify(signature);
|
||||||
|
signatureIsEffective(validationDate).verify(signature);
|
||||||
|
correctSignatureOverKey(primaryKey, primaryKey).verify(signature);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SignatureValidator hasValidPrimaryKeyBindingSignatureIfRequired(PGPPublicKey primaryKey, PGPPublicKey subkey, Policy policy, Date validationDate) {
|
||||||
|
return new SignatureValidator() {
|
||||||
|
@Override
|
||||||
|
public void verify(PGPSignature signature) throws SignatureValidationException {
|
||||||
|
if (!PublicKeyAlgorithm.fromId(signature.getKeyAlgorithm()).isSigningCapable()) {
|
||||||
|
// subkey is not signing capable -> No need to process embedded sigs
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
PGPSignatureList embeddedSignatures = SignatureSubpacketsUtil.getEmbeddedSignature(signature);
|
||||||
|
boolean hasValidPrimaryKeyBinding = false;
|
||||||
|
Map<PGPSignature, Exception> rejectedEmbeddedSigs = new ConcurrentHashMap<>();
|
||||||
|
for (PGPSignature embedded : embeddedSignatures) {
|
||||||
|
|
||||||
|
if (SignatureType.valueOf(embedded.getSignatureType()) == SignatureType.PRIMARYKEY_BINDING) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
signatureStructureIsAcceptable(subkey, policy).verify(embedded);
|
||||||
|
signatureIsEffective(validationDate).verify(embedded);
|
||||||
|
correctPrimaryKeyBindingSignature(primaryKey, subkey).verify(embedded);
|
||||||
|
|
||||||
|
hasValidPrimaryKeyBinding = true;
|
||||||
|
break;
|
||||||
|
} catch (SignatureValidationException e) {
|
||||||
|
rejectedEmbeddedSigs.put(embedded, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasValidPrimaryKeyBinding) {
|
||||||
|
throw new SignatureValidationException("Missing primary key binding signature on signing capable subkey " + Long.toHexString(subkey.getKeyID()), rejectedEmbeddedSigs);
|
||||||
|
}
|
||||||
|
} catch (PGPException e) {
|
||||||
|
throw new SignatureValidationException("Cannot process list of embedded signatures.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SignatureValidator signatureStructureIsAcceptable(PGPPublicKey signingKey, Policy policy) {
|
||||||
|
return new SignatureValidator() {
|
||||||
|
@Override
|
||||||
|
public void verify(PGPSignature signature) throws SignatureValidationException {
|
||||||
|
signatureIsNotMalformed(signingKey).verify(signature);
|
||||||
|
signatureDoesNotHaveCriticalUnknownNotations(policy.getNotationRegistry()).verify(signature);
|
||||||
|
signatureDoesNotHaveCriticalUnknownSubpackets().verify(signature);
|
||||||
|
signatureUsesAcceptableHashAlgorithm(policy).verify(signature);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SignatureValidator signatureUsesAcceptableHashAlgorithm(Policy policy) {
|
||||||
|
return new SignatureValidator() {
|
||||||
|
@Override
|
||||||
|
public void verify(PGPSignature signature) throws SignatureValidationException {
|
||||||
|
HashAlgorithm hashAlgorithm = HashAlgorithm.fromId(signature.getHashAlgorithm());
|
||||||
|
|
||||||
|
Policy.HashAlgorithmPolicy hashAlgorithmPolicy = null;
|
||||||
|
SignatureType type = SignatureType.valueOf(signature.getSignatureType());
|
||||||
|
if (type == SignatureType.CERTIFICATION_REVOCATION || type == SignatureType.KEY_REVOCATION || type == SignatureType.SUBKEY_REVOCATION) {
|
||||||
|
hashAlgorithmPolicy = policy.getRevocationSignatureHashAlgorithmPolicy();
|
||||||
|
} else {
|
||||||
|
hashAlgorithmPolicy = policy.getSignatureHashAlgorithmPolicy();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hashAlgorithmPolicy.isAcceptable(signature.getHashAlgorithm())) {
|
||||||
|
throw new SignatureValidationException("Signature uses inacceptable hash algorithm " + hashAlgorithm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SignatureValidator signatureDoesNotHaveCriticalUnknownNotations(NotationRegistry registry) {
|
||||||
|
return new SignatureValidator() {
|
||||||
|
@Override
|
||||||
|
public void verify(PGPSignature signature) throws SignatureValidationException {
|
||||||
|
List<NotationData> hashedNotations = SignatureSubpacketsUtil.getHashedNotationData(signature);
|
||||||
|
for (NotationData notation : hashedNotations) {
|
||||||
|
if (!notation.isCritical()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!registry.isKnownNotation(notation.getNotationName())) {
|
||||||
|
throw new SignatureValidationException("Signature contains unknown critical notation '" + notation.getNotationName() + "' in its hashed area.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SignatureValidator signatureDoesNotHaveCriticalUnknownSubpackets() {
|
||||||
|
return new SignatureValidator() {
|
||||||
|
@Override
|
||||||
|
public void verify(PGPSignature signature) throws SignatureValidationException {
|
||||||
|
PGPSignatureSubpacketVector hashedSubpackets = signature.getHashedSubPackets();
|
||||||
|
for (int criticalTag : hashedSubpackets.getCriticalTags()) {
|
||||||
|
try {
|
||||||
|
SignatureSubpacket.fromCode(criticalTag);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw new SignatureValidationException("Signature contains unknown critical subpacket of type " + Long.toHexString(criticalTag));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SignatureValidator signatureIsEffective(Date validationDate) {
|
||||||
|
return new SignatureValidator() {
|
||||||
|
@Override
|
||||||
|
public void verify(PGPSignature signature) throws SignatureValidationException {
|
||||||
|
Date signatureCreationTime = SignatureSubpacketsUtil.getSignatureCreationTime(signature).getTime();
|
||||||
|
// For hard revocations, skip the creation time check
|
||||||
|
if (!SignatureUtils.isHardRevocation(signature)) {
|
||||||
|
if (signatureCreationTime.after(validationDate)) {
|
||||||
|
throw new SignatureValidationException("Signature was created at " + signatureCreationTime + " and is therefore not yet valid at " + validationDate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Date signatureExpirationTime = SignatureSubpacketsUtil.getSignatureExpirationTimeAsDate(signature);
|
||||||
|
if (signatureExpirationTime != null && signatureExpirationTime.before(validationDate)) {
|
||||||
|
throw new SignatureValidationException("Signature is already expired (expiration: " + signatureExpirationTime + ", validation: " + validationDate + ")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SignatureValidator signatureIsNotMalformed(PGPPublicKey creator) {
|
||||||
|
return new SignatureValidator() {
|
||||||
|
@Override
|
||||||
|
public void verify(PGPSignature signature) throws SignatureValidationException {
|
||||||
|
signatureHasHashedCreationTime().verify(signature);
|
||||||
|
signatureDoesNotPredateSigningKey(creator).verify(signature);
|
||||||
|
signatureDoesNotPredateSigningKeyBindingDate(creator).verify(signature);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SignatureValidator signatureHasHashedCreationTime() {
|
||||||
|
return new SignatureValidator() {
|
||||||
|
@Override
|
||||||
|
public void verify(PGPSignature signature) throws SignatureValidationException {
|
||||||
|
SignatureCreationTime creationTime = SignatureSubpacketsUtil.getSignatureCreationTime(signature);
|
||||||
|
if (creationTime == null) {
|
||||||
|
throw new SignatureValidationException("Malformed signature. Signature has no signature creation time subpacket in its hashed area.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SignatureValidator signatureDoesNotPredateSigningKey(PGPPublicKey key) {
|
||||||
|
return new SignatureValidator() {
|
||||||
|
@Override
|
||||||
|
public void verify(PGPSignature signature) throws SignatureValidationException {
|
||||||
|
// TODO: Uncommenting the code below would mean that fake issuers would become a problem for sig verification
|
||||||
|
/*
|
||||||
|
long keyId = signature.getKeyID();
|
||||||
|
if (keyId == 0) {
|
||||||
|
OpenPgpV4Fingerprint fingerprint = SignatureSubpacketsUtil.getIssuerFingerprintAsOpenPgpV4Fingerprint(signature);
|
||||||
|
if (fingerprint == null) {
|
||||||
|
throw new SignatureValidationException("Signature does not contain an issuer-id, neither an issuer-fingerprint subpacket.");
|
||||||
|
}
|
||||||
|
keyId = fingerprint.getKeyId();
|
||||||
|
}
|
||||||
|
if (keyId != key.getKeyID()) {
|
||||||
|
throw new IllegalArgumentException("Signature was not created using key " + Long.toHexString(key.getKeyID()));
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
Date keyCreationTime = key.getCreationTime();
|
||||||
|
Date signatureCreationTime = signature.getCreationTime();
|
||||||
|
|
||||||
|
if (keyCreationTime.after(signatureCreationTime)) {
|
||||||
|
throw new SignatureValidationException("Signature predates its signing key (key creation: " + keyCreationTime + ", signature creation: " + signatureCreationTime + ")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SignatureValidator signatureDoesNotPredateSigningKeyBindingDate(PGPPublicKey signingKey) {
|
||||||
|
return new SignatureValidator() {
|
||||||
|
@Override
|
||||||
|
public void verify(PGPSignature signature) throws SignatureValidationException {
|
||||||
|
if (signingKey.isMasterKey()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
boolean predatesBindingSig = true;
|
||||||
|
Iterator<PGPSignature> bindingSignatures = signingKey.getSignaturesOfType(SignatureType.SUBKEY_BINDING.getCode());
|
||||||
|
if (!bindingSignatures.hasNext()) {
|
||||||
|
throw new SignatureValidationException("Signing subkey does not have a subkey binding signature.");
|
||||||
|
}
|
||||||
|
while (bindingSignatures.hasNext()) {
|
||||||
|
PGPSignature bindingSig = bindingSignatures.next();
|
||||||
|
if (!bindingSig.getCreationTime().after(signature.getCreationTime())) {
|
||||||
|
predatesBindingSig = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (predatesBindingSig) {
|
||||||
|
throw new SignatureValidationException("Signature was created before the signing key was bound to the key ring.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SignatureValidator correctPrimaryKeyBindingSignature(PGPPublicKey primaryKey, PGPPublicKey subkey) {
|
||||||
|
return new SignatureValidator() {
|
||||||
|
@Override
|
||||||
|
public void verify(PGPSignature signature) throws SignatureValidationException {
|
||||||
|
try {
|
||||||
|
signature.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), subkey);
|
||||||
|
boolean valid = signature.verifyCertification(primaryKey, subkey);
|
||||||
|
if (!valid) {
|
||||||
|
throw new SignatureValidationException("Primary Key Binding Signature is not correct.");
|
||||||
|
}
|
||||||
|
} catch (PGPException e) {
|
||||||
|
throw new SignatureValidationException("Cannot verify primary key binding signature correctness", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SignatureValidator correctSignatureOverKey(PGPPublicKey signer, PGPPublicKey signee) {
|
||||||
|
return new SignatureValidator() {
|
||||||
|
@Override
|
||||||
|
public void verify(PGPSignature signature) throws SignatureValidationException {
|
||||||
|
try {
|
||||||
|
signature.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), signer);
|
||||||
|
boolean valid = false;
|
||||||
|
if (signer.getKeyID() != signee.getKeyID()) {
|
||||||
|
valid = signature.verifyCertification(signer, signee);
|
||||||
|
} else {
|
||||||
|
valid = signature.verifyCertification(signee);
|
||||||
|
}
|
||||||
|
if (!valid) {
|
||||||
|
throw new SignatureValidationException("Signature is not correct.");
|
||||||
|
}
|
||||||
|
} catch (PGPException e) {
|
||||||
|
throw new SignatureValidationException("Cannot verify direct-key signature correctness", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SignatureValidator signatureIsCertification() {
|
||||||
|
return signatureIsOfType(
|
||||||
|
SignatureType.POSITIVE_CERTIFICATION,
|
||||||
|
SignatureType.CASUAL_CERTIFICATION,
|
||||||
|
SignatureType.GENERIC_CERTIFICATION,
|
||||||
|
SignatureType.NO_CERTIFICATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SignatureValidator signatureIsOfType(SignatureType... signatureTypes) {
|
||||||
|
return new SignatureValidator() {
|
||||||
|
@Override
|
||||||
|
public void verify(PGPSignature signature) throws SignatureValidationException {
|
||||||
|
SignatureType type = SignatureType.valueOf(signature.getSignatureType());
|
||||||
|
boolean valid = false;
|
||||||
|
for (SignatureType allowed : signatureTypes) {
|
||||||
|
if (type == allowed) {
|
||||||
|
valid = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!valid) {
|
||||||
|
throw new SignatureValidationException("Signature is of type " + type + " while only " + Arrays.toString(signatureTypes) + " are allowed here.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SignatureValidator correctSignatureOverUserId(String userId, PGPPublicKey certifiedKey, PGPPublicKey certifyingKey) {
|
||||||
|
return new SignatureValidator() {
|
||||||
|
@Override
|
||||||
|
public void verify(PGPSignature signature) throws SignatureValidationException {
|
||||||
|
try {
|
||||||
|
signature.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), certifyingKey);
|
||||||
|
boolean valid = signature.verifyCertification(userId, certifiedKey);
|
||||||
|
if (!valid) {
|
||||||
|
throw new SignatureValidationException("Signature over user-id '" + userId + "' is not correct.");
|
||||||
|
}
|
||||||
|
} catch (PGPException e) {
|
||||||
|
throw new SignatureValidationException("Cannot verify signature over user-id '" + userId + "'.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SignatureValidator correctSignatureOverUserAttributes(PGPUserAttributeSubpacketVector userAttributes, PGPPublicKey certifiedKey, PGPPublicKey certifyingKey) {
|
||||||
|
return new SignatureValidator() {
|
||||||
|
@Override
|
||||||
|
public void verify(PGPSignature signature) throws SignatureValidationException {
|
||||||
|
try {
|
||||||
|
signature.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), certifyingKey);
|
||||||
|
boolean valid = signature.verifyCertification(userAttributes, certifiedKey);
|
||||||
|
if (!valid) {
|
||||||
|
throw new SignatureValidationException("Signature over user-attribute vector is not correct.");
|
||||||
|
}
|
||||||
|
} catch (PGPException e) {
|
||||||
|
throw new SignatureValidationException("Cannot verify signature over user-attribute vector.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 Paul Schaub.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.pgpainless.signature;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
|
||||||
|
import org.bouncycastle.openpgp.PGPSignature;
|
||||||
|
|
||||||
|
public class SignatureValidityComparator implements Comparator<PGPSignature> {
|
||||||
|
|
||||||
|
private final SignatureCreationDateComparator.Order order;
|
||||||
|
private final SignatureCreationDateComparator creationDateComparator;
|
||||||
|
|
||||||
|
public SignatureValidityComparator() {
|
||||||
|
this(SignatureCreationDateComparator.DEFAULT_ORDER);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SignatureValidityComparator(SignatureCreationDateComparator.Order order) {
|
||||||
|
this.order = order;
|
||||||
|
this.creationDateComparator = new SignatureCreationDateComparator(order);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compare(PGPSignature one, PGPSignature two) {
|
||||||
|
int compareByCreationTime = creationDateComparator.compare(one, two);
|
||||||
|
boolean oneIsHard = SignatureUtils.isHardRevocation(one);
|
||||||
|
boolean twoIsHard = SignatureUtils.isHardRevocation(two);
|
||||||
|
|
||||||
|
// both have same "hardness", so compare creation time
|
||||||
|
if (oneIsHard == twoIsHard) {
|
||||||
|
return compareByCreationTime;
|
||||||
|
}
|
||||||
|
// favor the "harder" signature
|
||||||
|
return oneIsHard ? -1 : 1;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 Paul Schaub.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Classes related to OpenPGP signatures.
|
||||||
|
*/
|
||||||
|
package org.pgpainless.signature;
|
|
@ -13,7 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.pgpainless.util;
|
package org.pgpainless.signature.subpackets;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
|
@ -0,0 +1,359 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 Paul Schaub.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.pgpainless.signature.subpackets;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
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.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.bouncycastle.util.encoders.Hex;
|
||||||
|
import org.pgpainless.algorithm.SignatureSubpacket;
|
||||||
|
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 class 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 IssuerFingerprint getIssuerFingerprint(PGPSignature signature) {
|
||||||
|
return hashedOrUnhashed(signature, SignatureSubpacket.issuerFingerprint);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static OpenPgpV4Fingerprint getIssuerFingerprintAsOpenPgpV4Fingerprint(PGPSignature signature) {
|
||||||
|
IssuerFingerprint subpacket = getIssuerFingerprint(signature);
|
||||||
|
if (subpacket == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
OpenPgpV4Fingerprint fingerprint = new OpenPgpV4Fingerprint(Hex.encode(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 IssuerKeyID getIssuerKeyId(PGPSignature signature) {
|
||||||
|
return hashedOrUnhashed(signature, SignatureSubpacket.issuerKeyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 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 SignatureCreationTime getSignatureCreationTime(PGPSignature signature) {
|
||||||
|
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 SignatureExpirationTime getSignatureExpirationTime(PGPSignature signature) {
|
||||||
|
return hashed(signature, SignatureSubpacket.signatureExpirationTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Date getSignatureExpirationTimeAsDate(PGPSignature signature) {
|
||||||
|
SignatureExpirationTime subpacket = getSignatureExpirationTime(signature);
|
||||||
|
if (subpacket == null || subpacket.getTime() == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new Date(signature.getCreationTime().getTime() + 1000 * 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 KeyExpirationTime getKeyExpirationTime(PGPSignature signature) {
|
||||||
|
return hashed(signature, SignatureSubpacket.keyExpirationTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Date getKeyExpirationTimeAsDate(PGPSignature signature, PGPPublicKey signingKey) {
|
||||||
|
KeyExpirationTime subpacket = getKeyExpirationTime(signature);
|
||||||
|
if (subpacket == null || subpacket.getTime() == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (signature.getKeyID() != signingKey.getKeyID()) {
|
||||||
|
throw new IllegalArgumentException("Provided key (" + Long.toHexString(signingKey.getKeyID()) + ") did not create the signature (" + Long.toHexString(signature.getKeyID()) + ")");
|
||||||
|
}
|
||||||
|
return new Date(signingKey.getCreationTime().getTime() + 1000 * subpacket.getTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 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 PreferredAlgorithms getPreferredSymmetricAlgorithms(PGPSignature signature) {
|
||||||
|
return hashed(signature, SignatureSubpacket.preferredSymmetricAlgorithms);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the hash algorithm preferences from the signatures hashed area.
|
||||||
|
*
|
||||||
|
* @param signature signature
|
||||||
|
* @return hash algo prefs
|
||||||
|
*/
|
||||||
|
public static PreferredAlgorithms getPreferredHashAlgorithms(PGPSignature signature) {
|
||||||
|
return hashed(signature, SignatureSubpacket.preferredHashAlgorithms);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the compression algorithm preferences from the signatures hashed area.
|
||||||
|
*
|
||||||
|
* @param signature signature
|
||||||
|
* @return compression algo prefs
|
||||||
|
*/
|
||||||
|
public static PreferredAlgorithms getPreferredCompressionAlgorithms(PGPSignature signature) {
|
||||||
|
return hashed(signature, SignatureSubpacket.preferredCompressionAlgorithms);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the primary user-id subpacket from the signatures hashed area.
|
||||||
|
*
|
||||||
|
* @param signature signature
|
||||||
|
* @return primary user id
|
||||||
|
*/
|
||||||
|
public static 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 KeyFlags getKeyFlags(PGPSignature signature) {
|
||||||
|
return hashed(signature, SignatureSubpacket.keyFlags);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the features subpacket from the signatures hashed area.
|
||||||
|
*
|
||||||
|
* @param signature signature
|
||||||
|
* @return features subpacket
|
||||||
|
*/
|
||||||
|
public static Features getFeatures(PGPSignature signature) {
|
||||||
|
return hashed(signature, SignatureSubpacket.features);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 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 List<NotationData> getHashedNotationData(PGPSignature signature) {
|
||||||
|
NotationData[] notations = signature.getHashedSubPackets().getNotationDataOccurrences();
|
||||||
|
return Arrays.asList(notations);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the notation data subpackets from the signatures unhashed area.
|
||||||
|
*
|
||||||
|
* @param signature signture
|
||||||
|
* @return unhashed notations
|
||||||
|
*/
|
||||||
|
public static List<NotationData> getUnhashedNotationData(PGPSignature signature) {
|
||||||
|
NotationData[] notations = signature.getUnhashedSubPackets().getNotationDataOccurrences();
|
||||||
|
return Arrays.asList(notations);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the revocation key subpacket from the signatures hashed area.
|
||||||
|
*
|
||||||
|
* @param signature signature
|
||||||
|
* @return revocation key
|
||||||
|
*/
|
||||||
|
public static 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 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 List<IntendedRecipientFingerprint> getIntendedRecipientFingerprints(PGPSignature signature) {
|
||||||
|
org.bouncycastle.bcpg.SignatureSubpacket[] subpackets = signature.getHashedSubPackets().getSubpackets(SignatureSubpacket.intendedRecipientFingerprint.getCode());
|
||||||
|
List<IntendedRecipientFingerprint> 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
|
||||||
|
*/
|
||||||
|
public static 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.
|
||||||
|
* TODO: Can this packet also be placed in the unhashed area?
|
||||||
|
*
|
||||||
|
* @param signature signature
|
||||||
|
* @return exportable certification subpacket
|
||||||
|
*/
|
||||||
|
public static Exportable getExportableCertification(PGPSignature signature) {
|
||||||
|
return hashed(signature, SignatureSubpacket.exportableCertification);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the trust signature packet from the signatures hashed area.
|
||||||
|
*
|
||||||
|
* @param signature signature
|
||||||
|
* @return trust signature subpacket
|
||||||
|
*/
|
||||||
|
public static TrustSignature getTrustSignature(PGPSignature signature) {
|
||||||
|
return hashed(signature, SignatureSubpacket.trustSignature);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <P extends org.bouncycastle.bcpg.SignatureSubpacket> P hashed(PGPSignature signature, SignatureSubpacket type) {
|
||||||
|
return getSignatureSubpacket(signature.getHashedSubPackets(), type);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <P extends org.bouncycastle.bcpg.SignatureSubpacket> P unhashed(PGPSignature signature, SignatureSubpacket type) {
|
||||||
|
return getSignatureSubpacket(signature.getUnhashedSubPackets(), type);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <P extends org.bouncycastle.bcpg.SignatureSubpacket> P hashedOrUnhashed(PGPSignature signature, SignatureSubpacket type) {
|
||||||
|
P hashedSubpacket = hashed(signature, type);
|
||||||
|
return hashedSubpacket != null ? hashedSubpacket : unhashed(signature, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the last occurence of a subpacket type in the given signature subpacket vector.
|
||||||
|
*
|
||||||
|
* @param vector subpacket vector (hashed/unhashed)
|
||||||
|
* @param type subpacket type
|
||||||
|
* @param <P> generic return type of the subpacket
|
||||||
|
* @return last occurrence of the subpacket in the vector
|
||||||
|
*/
|
||||||
|
public static <P extends org.bouncycastle.bcpg.SignatureSubpacket> P getSignatureSubpacket(PGPSignatureSubpacketVector vector, SignatureSubpacket type) {
|
||||||
|
org.bouncycastle.bcpg.SignatureSubpacket[] allPackets = vector.getSubpackets(type.getCode());
|
||||||
|
if (allPackets.length == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (P) allPackets[allPackets.length - 1]; // return last
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 Paul Schaub.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Classes related to OpenPGP signatures.
|
||||||
|
*/
|
||||||
|
package org.pgpainless.signature.subpackets;
|
|
@ -19,7 +19,9 @@ import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -29,6 +31,8 @@ import javax.annotation.Nonnull;
|
||||||
|
|
||||||
import org.bouncycastle.openpgp.PGPException;
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
import org.bouncycastle.openpgp.PGPKeyRing;
|
import org.bouncycastle.openpgp.PGPKeyRing;
|
||||||
|
import org.bouncycastle.openpgp.PGPMarker;
|
||||||
|
import org.bouncycastle.openpgp.PGPObjectFactory;
|
||||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||||
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||||
|
@ -36,19 +40,28 @@ import org.bouncycastle.openpgp.PGPSecretKey;
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
||||||
import org.bouncycastle.openpgp.PGPSignature;
|
import org.bouncycastle.openpgp.PGPSignature;
|
||||||
|
import org.bouncycastle.openpgp.PGPSignatureList;
|
||||||
import org.bouncycastle.openpgp.PGPSignatureSubpacketVector;
|
import org.bouncycastle.openpgp.PGPSignatureSubpacketVector;
|
||||||
import org.bouncycastle.openpgp.PGPUtil;
|
import org.bouncycastle.openpgp.PGPUtil;
|
||||||
import org.bouncycastle.util.io.Streams;
|
import org.bouncycastle.util.io.Streams;
|
||||||
import org.pgpainless.algorithm.KeyFlag;
|
import org.pgpainless.algorithm.KeyFlag;
|
||||||
|
import org.pgpainless.implementation.ImplementationFactory;
|
||||||
import org.pgpainless.util.selection.key.PublicKeySelectionStrategy;
|
import org.pgpainless.util.selection.key.PublicKeySelectionStrategy;
|
||||||
import org.pgpainless.util.selection.key.impl.NoRevocation;
|
|
||||||
import org.pgpainless.util.selection.key.impl.KeyBelongsToKeyRing;
|
|
||||||
import org.pgpainless.util.selection.key.impl.And;
|
import org.pgpainless.util.selection.key.impl.And;
|
||||||
|
import org.pgpainless.util.selection.key.impl.KeyBelongsToKeyRing;
|
||||||
|
import org.pgpainless.util.selection.key.impl.NoRevocation;
|
||||||
|
|
||||||
public class BCUtil {
|
public class BCUtil {
|
||||||
|
|
||||||
private static final Logger LOGGER = Logger.getLogger(BCUtil.class.getName());
|
private static final Logger LOGGER = Logger.getLogger(BCUtil.class.getName());
|
||||||
|
|
||||||
|
public static Date getExpirationDate(Date creationDate, long validSeconds) {
|
||||||
|
if (validSeconds == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new Date(creationDate.getTime() + 1000 * validSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
PGPXxxKeyRing -> PGPXxxKeyRingCollection
|
PGPXxxKeyRing -> PGPXxxKeyRingCollection
|
||||||
*/
|
*/
|
||||||
|
@ -245,4 +258,18 @@ public class BCUtil {
|
||||||
long keyId) {
|
long keyId) {
|
||||||
return ring.getSecretKey(keyId) != null;
|
return ring.getSecretKey(keyId) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static PGPSignatureList readSignatures(String encoding) throws IOException {
|
||||||
|
InputStream inputStream = getPgpDecoderInputStream(encoding.getBytes(Charset.forName("UTF8")));
|
||||||
|
PGPObjectFactory objectFactory = new PGPObjectFactory(inputStream, ImplementationFactory.getInstance().getKeyFingerprintCalculator());
|
||||||
|
Object next = objectFactory.nextObject();
|
||||||
|
while (next != null) {
|
||||||
|
if (next instanceof PGPMarker) {
|
||||||
|
next = objectFactory.nextObject();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return (PGPSignatureList) next;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,22 +25,14 @@ import java.util.Set;
|
||||||
*
|
*
|
||||||
* To add a notation name, call {@link #addKnownNotation(String)}.
|
* To add a notation name, call {@link #addKnownNotation(String)}.
|
||||||
*/
|
*/
|
||||||
public final class NotationRegistry {
|
public class NotationRegistry {
|
||||||
|
|
||||||
private static NotationRegistry INSTANCE;
|
|
||||||
private final Set<String> knownNotations = new HashSet<>();
|
private final Set<String> knownNotations = new HashSet<>();
|
||||||
|
|
||||||
private NotationRegistry() {
|
public NotationRegistry() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static NotationRegistry getInstance() {
|
|
||||||
if (INSTANCE == null) {
|
|
||||||
INSTANCE = new NotationRegistry();
|
|
||||||
}
|
|
||||||
return INSTANCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a known notation name into the registry.
|
* Add a known notation name into the registry.
|
||||||
* This will cause critical notations with that name to no longer invalidate the signature.
|
* This will cause critical notations with that name to no longer invalidate the signature.
|
||||||
|
|
34
pgpainless-core/src/main/java/org/pgpainless/util/Tuple.java
Normal file
34
pgpainless-core/src/main/java/org/pgpainless/util/Tuple.java
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 Paul Schaub.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.pgpainless.util;
|
||||||
|
|
||||||
|
public class Tuple<A, B> {
|
||||||
|
private final A a;
|
||||||
|
private final B b;
|
||||||
|
|
||||||
|
public Tuple(A a, B b) {
|
||||||
|
this.a = a;
|
||||||
|
this.b = b;
|
||||||
|
}
|
||||||
|
|
||||||
|
public A getFirst() {
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
public B getSecond() {
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,311 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 Paul Schaub.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.pgpainless.util.selection.key;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
|
import org.bouncycastle.openpgp.PGPKeyRing;
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||||
|
import org.bouncycastle.openpgp.PGPSignature;
|
||||||
|
import org.pgpainless.algorithm.CompressionAlgorithm;
|
||||||
|
import org.pgpainless.algorithm.HashAlgorithm;
|
||||||
|
import org.pgpainless.algorithm.KeyFlag;
|
||||||
|
import org.pgpainless.algorithm.SignatureType;
|
||||||
|
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||||
|
import org.pgpainless.signature.SignatureUtils;
|
||||||
|
import org.pgpainless.util.CollectionUtils;
|
||||||
|
import org.pgpainless.signature.SelectSignatureFromKey;
|
||||||
|
|
||||||
|
public abstract class SelectPublicKey {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(SelectPublicKey.class.getName());
|
||||||
|
|
||||||
|
public abstract boolean accept(PGPPublicKey publicKey, PGPKeyRing keyRing);
|
||||||
|
|
||||||
|
public List<PGPPublicKey> selectPublicKeys(PGPKeyRing keyRing) {
|
||||||
|
List<PGPPublicKey> selected = new ArrayList<>();
|
||||||
|
List<PGPPublicKey> publicKeys = CollectionUtils.iteratorToList(keyRing.getPublicKeys());
|
||||||
|
for (PGPPublicKey publicKey : publicKeys) {
|
||||||
|
if (accept(publicKey, keyRing)) {
|
||||||
|
selected.add(publicKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PGPPublicKey firstMatch(PGPKeyRing keyRing) {
|
||||||
|
List<PGPPublicKey> selected = selectPublicKeys(keyRing);
|
||||||
|
if (selected.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return selected.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SelectPublicKey isPrimaryKey() {
|
||||||
|
return new SelectPublicKey() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(PGPPublicKey publicKey, PGPKeyRing keyRing) {
|
||||||
|
return publicKey.isMasterKey() && keyRing.getPublicKey().getKeyID() == publicKey.getKeyID();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SelectPublicKey isSubKey() {
|
||||||
|
return new SelectPublicKey() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(PGPPublicKey publicKey, PGPKeyRing keyRing) {
|
||||||
|
if (isPrimaryKey().accept(publicKey, keyRing)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
PGPPublicKey primaryKey = keyRing.getPublicKey();
|
||||||
|
SelectSignatureFromKey bindingSigSelector = SelectSignatureFromKey.isValidSubkeyBindingSignature(primaryKey, publicKey);
|
||||||
|
|
||||||
|
Iterator<PGPSignature> bindingSigs = publicKey.getSignaturesOfType(SignatureType.SUBKEY_BINDING.getCode());
|
||||||
|
while (bindingSigs.hasNext()) {
|
||||||
|
if (bindingSigSelector.accept(bindingSigs.next(), publicKey, keyRing)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SelectPublicKey validForUserId(String userId) {
|
||||||
|
return validForUserId(userId, new Date());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SelectPublicKey validForUserId(String userId, Date validationDate) {
|
||||||
|
return new SelectPublicKey() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(PGPPublicKey publicKey, PGPKeyRing keyRing) {
|
||||||
|
PGPPublicKey primaryKey = keyRing.getPublicKey();
|
||||||
|
|
||||||
|
// Has userid
|
||||||
|
List<String> userIds = CollectionUtils.iteratorToList(primaryKey.getUserIDs());
|
||||||
|
if (!userIds.contains(userId)) {
|
||||||
|
LOGGER.log(Level.INFO, "Keyring " + Long.toHexString(primaryKey.getKeyID()) + " does not contain user-id '" + userId + "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
// is primary key revoked
|
||||||
|
if (isRevoked(validationDate).accept(primaryKey, keyRing)) {
|
||||||
|
LOGGER.log(Level.INFO, "Primary key " + Long.toHexString(primaryKey.getKeyID()) + " has been revoked.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// is userid expired
|
||||||
|
if (isExpired(userId, validationDate).accept(primaryKey, keyRing)) {
|
||||||
|
LOGGER.log(Level.INFO, "Primary key " + Long.toHexString(primaryKey.getKeyID()) + " has expired.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// is userid revoked
|
||||||
|
if (isUserIdRevoked(userId, validationDate).accept(primaryKey, keyRing)) {
|
||||||
|
LOGGER.log(Level.INFO, "Primary key " + Long.toHexString(primaryKey.getKeyID()) + " has been revoked.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserId on primary key valid
|
||||||
|
try {
|
||||||
|
boolean userIdValid = SignatureUtils.isUserIdValid(primaryKey, userId);
|
||||||
|
if (!userIdValid) {
|
||||||
|
LOGGER.log(Level.INFO, "User-id '" + userId + "' is not valid for key " + Long.toHexString(primaryKey.getKeyID()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (PGPException e) {
|
||||||
|
LOGGER.log(Level.INFO, "Could not verify signature on primary key " + Long.toHexString(primaryKey.getKeyID()) + " and user-id '" + userId + "'", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// is primary key
|
||||||
|
if (publicKey == primaryKey) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// is subkey
|
||||||
|
if (!isSubKey().accept(publicKey, keyRing)) {
|
||||||
|
LOGGER.log(Level.INFO, "Key " + Long.toHexString(publicKey.getKeyID()) + " is not valid subkey of key " + Long.toHexString(primaryKey.getKeyID()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// is subkey revoked
|
||||||
|
if (isRevoked(validationDate).accept(publicKey, keyRing)) {
|
||||||
|
LOGGER.log(Level.INFO, "Subkey " + Long.toHexString(publicKey.getKeyID()) + " of key " + Long.toHexString(primaryKey.getKeyID()) + " is revoked");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SelectPublicKey isRevoked(Date validationDate) {
|
||||||
|
return new SelectPublicKey() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(PGPPublicKey publicKey, PGPKeyRing keyRing) {
|
||||||
|
if (publicKey.isMasterKey()) {
|
||||||
|
if (!publicKey.hasRevocation()) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
SelectSignatureFromKey validRevocation = SelectSignatureFromKey.isValidKeyRevocationSignature(publicKey);
|
||||||
|
Iterator<PGPSignature> revSigIt = publicKey.getSignaturesOfType(SignatureType.KEY_REVOCATION.getCode());
|
||||||
|
List<PGPSignature> revSigs = CollectionUtils.iteratorToList(revSigIt);
|
||||||
|
List<PGPSignature> validRevSigs = validRevocation.select(revSigs, publicKey, keyRing);
|
||||||
|
return !validRevSigs.isEmpty();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return publicKey.hasRevocation() || keyRing.getPublicKey().hasRevocation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SelectPublicKey isExpired(String userId, Date validationDate) {
|
||||||
|
return new SelectPublicKey() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(PGPPublicKey publicKey, PGPKeyRing keyRing) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SelectPublicKey isUserIdRevoked(String userId, Date validationDate) {
|
||||||
|
return new SelectPublicKey() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(PGPPublicKey publicKey, PGPKeyRing keyRing) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SelectPublicKey hasKeyRevocationSignature() {
|
||||||
|
return new SelectPublicKey() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(PGPPublicKey publicKey, PGPKeyRing keyRing) {
|
||||||
|
Iterator<PGPSignature> it = publicKey.getSignatures();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
PGPSignature signature = it.next();
|
||||||
|
if (SelectSignatureFromKey.isValidKeyRevocationSignature(publicKey).accept(signature, publicKey, keyRing)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SelectPublicKey hasSubkeyRevocationSignature() {
|
||||||
|
return new SelectPublicKey() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(PGPPublicKey publicKey, PGPKeyRing keyRing) {
|
||||||
|
Iterator<PGPSignature> it = publicKey.getKeySignatures();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
PGPSignature signature = it.next();
|
||||||
|
if (SelectSignatureFromKey.isValidSubkeyRevocationSignature(publicKey, keyRing.getPublicKey()).accept(signature, publicKey, keyRing)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SelectPublicKey isSubkeyOfRevokedPrimaryKey() {
|
||||||
|
return new SelectPublicKey() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(PGPPublicKey publicKey, PGPKeyRing keyRing) {
|
||||||
|
return isSubKey().accept(publicKey, keyRing)
|
||||||
|
&& SelectPublicKey.hasKeyRevocationSignature().accept(keyRing.getPublicKey(), keyRing);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SelectPublicKey hasKeyFlag(String userId, KeyFlag keyFlag) {
|
||||||
|
return new SelectPublicKey() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(PGPPublicKey publicKey, PGPKeyRing keyRing) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SelectPublicKey supportsAlgorithm(SymmetricKeyAlgorithm symmetricKeyAlgorithm) {
|
||||||
|
return new SelectPublicKey() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(PGPPublicKey publicKey, PGPKeyRing keyRing) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SelectPublicKey supportsAlgorithm(HashAlgorithm hashAlgorithm) {
|
||||||
|
return new SelectPublicKey() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(PGPPublicKey publicKey, PGPKeyRing keyRing) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SelectPublicKey supportsAlgorithm(CompressionAlgorithm compressionAlgorithm) {
|
||||||
|
return new SelectPublicKey() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(PGPPublicKey publicKey, PGPKeyRing keyRing) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SelectPublicKey and(SelectPublicKey... selectors) {
|
||||||
|
return new SelectPublicKey() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(PGPPublicKey publicKey, PGPKeyRing keyRing) {
|
||||||
|
for (SelectPublicKey selector : selectors) {
|
||||||
|
if (!selector.accept(publicKey, keyRing)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SelectPublicKey or(SelectPublicKey... selectors) {
|
||||||
|
return new SelectPublicKey() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(PGPPublicKey publicKey, PGPKeyRing keyRing) {
|
||||||
|
boolean accept = false;
|
||||||
|
for (SelectPublicKey selector : selectors) {
|
||||||
|
accept |= selector.accept(publicKey, keyRing);
|
||||||
|
}
|
||||||
|
return accept;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SelectPublicKey not(SelectPublicKey selector) {
|
||||||
|
return new SelectPublicKey() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(PGPPublicKey publicKey, PGPKeyRing keyRing) {
|
||||||
|
return !selector.accept(publicKey, keyRing);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -44,7 +44,10 @@ public class HasAnyKeyFlagSelectionStrategy {
|
||||||
@Override
|
@Override
|
||||||
public boolean accept(PGPPublicKey key) {
|
public boolean accept(PGPPublicKey key) {
|
||||||
Iterator<PGPSignature> signatures = key.getSignatures();
|
Iterator<PGPSignature> signatures = key.getSignatures();
|
||||||
int flags = signatures.next().getHashedSubPackets().getKeyFlags();
|
int flags = 0;
|
||||||
|
while (signatures.hasNext()) {
|
||||||
|
flags = signatures.next().getHashedSubPackets().getKeyFlags();
|
||||||
|
}
|
||||||
return (keyFlagMask & flags) != 0;
|
return (keyFlagMask & flags) != 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 Paul Schaub.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.bouncycastle;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
|
||||||
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||||
|
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.pgpainless.PGPainless;
|
||||||
|
import org.pgpainless.key.util.KeyRingUtils;
|
||||||
|
|
||||||
|
public class PGPPublicKeyRingTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Learning test to see if BC also makes userids available on subkeys.
|
||||||
|
* It does not.
|
||||||
|
*
|
||||||
|
* see also https://security.stackexchange.com/questions/92635/is-it-possible-to-assign-different-uids-to-subkeys-for-the-purpose-of-having-mul
|
||||||
|
*
|
||||||
|
* @throws InvalidAlgorithmParameterException
|
||||||
|
* @throws NoSuchAlgorithmException
|
||||||
|
* @throws PGPException
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void subkeysDoNotHaveUserIDsTest() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException {
|
||||||
|
PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing().simpleEcKeyRing("primary@user.id");
|
||||||
|
PGPPublicKeyRing publicKeys = KeyRingUtils.publicKeyRingFrom(secretKeys);
|
||||||
|
PGPPublicKey primaryKey = publicKeys.getPublicKey();
|
||||||
|
for (PGPPublicKey subkey : publicKeys) {
|
||||||
|
Iterator<String> userIds = subkey.getUserIDs();
|
||||||
|
if (primaryKey == subkey) {
|
||||||
|
assertEquals("primary@user.id", userIds.next());
|
||||||
|
assertFalse(userIds.hasNext());
|
||||||
|
} else {
|
||||||
|
assertFalse(userIds.hasNext());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,9 +17,16 @@ package org.junit;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
public class JUtils {
|
public class JUtils {
|
||||||
|
|
||||||
public static void assertEquals(long a, long b, long delta) {
|
public static void assertEquals(long a, long b, long delta) {
|
||||||
assertTrue(a - delta <= b && a + delta >= b);
|
assertTrue(a - delta <= b && a + delta >= b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void comparatorLearningTest() {
|
||||||
|
assertEquals(-1, Integer.compare(5,6), 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ package org.pgpainless.encryption_signing;
|
||||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
|
@ -36,6 +37,7 @@ import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||||
import org.bouncycastle.openpgp.PGPSignature;
|
import org.bouncycastle.openpgp.PGPSignature;
|
||||||
import org.bouncycastle.util.io.Streams;
|
import org.bouncycastle.util.io.Streams;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
import org.junit.jupiter.params.provider.MethodSource;
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
import org.pgpainless.PGPainless;
|
import org.pgpainless.PGPainless;
|
||||||
|
@ -280,4 +282,68 @@ public class EncryptDecryptTest {
|
||||||
OpenPgpMetadata metadata = verifier.getResult();
|
OpenPgpMetadata metadata = verifier.getResult();
|
||||||
assertFalse(metadata.getVerifiedSignatures().isEmpty());
|
assertFalse(metadata.getVerifiedSignatures().isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void expiredSubkeyBacksigTest() throws IOException {
|
||||||
|
String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
|
||||||
|
"\n" +
|
||||||
|
"xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" +
|
||||||
|
"/seOXpgecTdOcVttfzC8ycIKrt3aQTiwOG/ctaR4Bk/t6ayNFfdUNxHWk4WCKzdz\n" +
|
||||||
|
"/56fW2O0F23qIRd8UUJp5IIlN4RDdRCtdhVQIAuzvp2oVy/LaS2kxQoKvph/5pQ/\n" +
|
||||||
|
"5whqsyroEWDJoSV0yOb25B/iwk/pLUFoyhDG9bj0kIzDxrEqW+7Ba8nocQlecMF3\n" +
|
||||||
|
"X5KMN5kp2zraLv9dlBBpWW43XktjcCZgMy20SouraVma8Je/ECwUWYUiAZxLIlMv\n" +
|
||||||
|
"9CurEOtxUw6N3RdOtLmYZS9uEnn5y1UkF88o8Nku890uk6BrewFzJyLAx5wRZ4F0\n" +
|
||||||
|
"qV/yq36UWQ0JB/AUGhHVPdFf6pl6eaxBwT5GXvbBUibtf8YI2og5RsgTWtXfU7eb\n" +
|
||||||
|
"SGXrl5ZMpbA6mbfhd0R8aPxWfmDWiIOhBufhMCvUHh1sApMKVZnvIff9/0Dca3wb\n" +
|
||||||
|
"vLIwa3T4CyshfT0AEQEAAc0hQm9iIEJhYmJhZ2UgPGJvYkBvcGVucGdwLmV4YW1w\n" +
|
||||||
|
"bGU+wsFIBBMBCgB8BYJfUbC4AgsJCRD7/MgqAV5zMEcUAAAAAAAeACBzYWx0QG5v\n" +
|
||||||
|
"dGF0aW9ucy5zZXF1b2lhLXBncC5vcmcEc0Prq/Ohwr794nDXrgZXDdDq38GOMsus\n" +
|
||||||
|
"hDqEwk/zJgMVCAoCmwECHgEWIQTRpm4aI7GCyZgPeIz7/MgqAV5zMAAA3rIL/3cI\n" +
|
||||||
|
"WywtBrcW40S3lGoQL8zhl4wrI/HiXUGwEvEB/kfyfNk3uS73d5OgbOk4Xiw8QuCK\n" +
|
||||||
|
"AX8oyAypYheb1M2Q7VW+Iohl6Jpq8QppUX7YKugnH4bYIZsdVQw5VT+69UsuHfj0\n" +
|
||||||
|
"x6FKXw3ums2QhpB6XErd/G/npJtaK7LGoMo9ZRGKIdS+KwaXp0jU4+pgNVnzfRCA\n" +
|
||||||
|
"4AcmRCsHI4pgoIbQ79qCdpe9KJLf+blkNZFKCUXrAegbmaQ8wG4MdH4K/hnM0HaG\n" +
|
||||||
|
"MWiR0CKuKn8Mx4KHtTQz74jpHQAkvlqxgGulyfx+Kl6e8y4+AatJAG/62/3brIAw\n" +
|
||||||
|
"+tFXYxnONaQm/22h84YvSp/w4DqtuqHxrkkPjjgdE4QzBuVGd6PEa/59spagX6UC\n" +
|
||||||
|
"+UMyyVE2MadXPO1gkPmEnBcn/nOlEU3ekpysC3D2Etdxwjhso+MeWFUbQlBDdgVi\n" +
|
||||||
|
"Sk/B/HjCPLmtH1FELnAe778L0exe+G2hLad8UHcnc2INtwFSBNUSIEYbbsYR0s7A\n" +
|
||||||
|
"zQRdpZzyAQwA1jC/XGxjK6ddgrRfW9j+s/U00++EvIsgTs2kr3Rg0GP7FLWV0YNt\n" +
|
||||||
|
"R1mpl55/bEl7yAxCDTkOgPUMXcaKlnQh6zrlt6H53mF6Bvs3inOHQvOsGtU0dqvb\n" +
|
||||||
|
"1vkTF0juLiJgPlM7pWv+pNQ6IA39vKoQsTMBv4v5vYNXP9GgKbg8inUNT17BxzZY\n" +
|
||||||
|
"Hfw5+q63ectgDm2on1e8CIRCZ76oBVwzdkVxoy3gjh1eENlk2D4P0uJNZzF1Q8GV\n" +
|
||||||
|
"67yLANGMCDICE/OkWn6daipYDzW4iJQtYPUWP4hWhjdm+CK+hg6IQUEn2Vtvi16D\n" +
|
||||||
|
"2blRP8BpUNNa4fNuylWVuJV76rIHvsLZ1pbM3LHpRgE8s6jivS3Rz3WRs0TmWCNn\n" +
|
||||||
|
"vHPqWizQ3VTy+r3UQVJ5AmhJDrZdZq9iaUIuZ01PoE1+CHiJwuxPtWvVAxf2POcm\n" +
|
||||||
|
"1M/F1fK1J0e+lKlQuyonTXqXR22Y41wrfP2aPk3nPSTW2DUAf3vRMZg57ZpRxLEh\n" +
|
||||||
|
"EMxcM4/LMR+PABEBAAHCw0IEGAEKAnYFgl9RsLgJEPv8yCoBXnMwRxQAAAAAAB4A\n" +
|
||||||
|
"IHNhbHRAbm90YXRpb25zLnNlcXVvaWEtcGdwLm9yZykWtbTuOtDrg4F5s48NrAHA\n" +
|
||||||
|
"kwkoLb8ZgAbb9VV8JPKRApsCwUKgBBkBCgB1BYJfUbC4BYMAeEzgCRB8L6pN+Tw3\n" +
|
||||||
|
"skcUAAAAAAAeACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmemJbX0gOh6\n" +
|
||||||
|
"Z+WJo5dyEuNnG7CDklyLHJ8BY2QKoO88ehYhBB3c4V8JIXzuLzs3YHwvqk35PDey\n" +
|
||||||
|
"AAAPrwv+OSxllLwrRUB0BqRYS2/D1qFHFOn0qBOMJaL0X2yjint67SeHosxmvqSg\n" +
|
||||||
|
"5tnQmaHljFrMZkf6PSGYdz0VwalT8XaubcGyljSxrgc7Qs5jdxKL5IhTfjEb0Q4v\n" +
|
||||||
|
"8TSp3esG02ZafGAZSwIIW1RfUEMk+XHciEk2pRDkraCAlcCvqL2En+eNLCqWzpTI\n" +
|
||||||
|
"Fcp0lb2JxRlozzqpfVNq++UXaHaqrGflbrTn4x+1i6zuxCVkjt4gHjQRLACDmEFk\n" +
|
||||||
|
"mSZxqYZmQdvEfkdSg2XgTjg+QhHunpQyCbxrW5R4qYgm7yjctgv9keVDbIy2lRIM\n" +
|
||||||
|
"kNWZhZWijw1SxPGVWlKVizi+pWZyX9NBrTAj/ES/HZrLda52PR1BKSE4kG74T/73\n" +
|
||||||
|
"V/jnqYp0jGI/M3y79DRq2tlO5p6Jp+OcmU2SyvItaNhoateGndLIVPZfAT69avbY\n" +
|
||||||
|
"tMoEbsA/biVL4xN9SqaLian4ow9/pVm/z4Ej6zSRZUC01hZBQWD02z0ntU7t0CPR\n" +
|
||||||
|
"R58XC9znFiEE0aZuGiOxgsmYD3iM+/zIKgFeczAAAED5C/975SfSeub9RJHilYFA\n" +
|
||||||
|
"eeeHU6ZaSpOy0/ZrwSUFmvDrxowiCNn7sYZEZmIBVZ/nIlfbCUUTesIF92aLkIZe\n" +
|
||||||
|
"EMQUiXP0/HtnAx1duQ8htdb+X/EhuWPPJ7hF5bA6AB1oXVKn3lpggHzauGSilI5m\n" +
|
||||||
|
"dPXXVdDUWuDQfSn459UOv4PwB52uLtGZK3iprVgYD3RzSWktHMhMvcB2GXNQlfyo\n" +
|
||||||
|
"yWewq9p+wwbIFUFZYMRIGjJNSc6aQcEHusIn85E+Uid/hrDIiblbvQA+7ONcoaqL\n" +
|
||||||
|
"DiLSL+bh1/usrmzccUK01nLMmTnG03vU3WR3yqmDlzgU/S3XfZRPECwr6AzNSXoe\n" +
|
||||||
|
"d4u9/SPt2VBxGtZ0yA4PXgO6PbZC6EIZqmgW5oKjSWZwkryQLGKji+vYJU1FzM+3\n" +
|
||||||
|
"qO6PYqLVGf97n6LS2xD10rrJ2aUq0CQ/M5ykRVsT6HifV9wPiPzR8ilcXWRT8CQ2\n" +
|
||||||
|
"Ks2WqI282/DM+Lq/GCSd2nXtS3/KwErTFiF1uHi/N3TwdWA=\n" +
|
||||||
|
"=j1TE\n" +
|
||||||
|
"-----END PGP PUBLIC KEY BLOCK-----\n";
|
||||||
|
|
||||||
|
PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(key);
|
||||||
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
|
assertThrows(IllegalArgumentException.class, () ->
|
||||||
|
PGPainless.encryptAndOrSign().onOutputStream(outputStream)
|
||||||
|
.toRecipients(publicKeys));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,7 +72,7 @@ public class SigningTest {
|
||||||
.toRecipients(keys)
|
.toRecipients(keys)
|
||||||
.andToSelf(KeyRingUtils.publicKeyRingFrom(cryptieKeys))
|
.andToSelf(KeyRingUtils.publicKeyRingFrom(cryptieKeys))
|
||||||
.usingAlgorithms(SymmetricKeyAlgorithm.AES_192, HashAlgorithm.SHA384, CompressionAlgorithm.ZIP)
|
.usingAlgorithms(SymmetricKeyAlgorithm.AES_192, HashAlgorithm.SHA384, CompressionAlgorithm.ZIP)
|
||||||
.signWith(SecretKeyRingProtector.unlockSingleKeyWith(TestKeys.CRYPTIE_PASSPHRASE, cryptieSigningKey), cryptieSigningKey)
|
.signWith(SecretKeyRingProtector.unlockSingleKeyWith(TestKeys.CRYPTIE_PASSPHRASE, cryptieSigningKey), cryptieKeys)
|
||||||
.signCanonicalText()
|
.signCanonicalText()
|
||||||
.asciiArmor();
|
.asciiArmor();
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,289 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 Paul Schaub.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.pgpainless.key;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.pgpainless.PGPainless;
|
||||||
|
import org.pgpainless.key.info.KeyRingInfo;
|
||||||
|
import org.pgpainless.util.ArmorUtils;
|
||||||
|
import org.pgpainless.util.CollectionUtils;
|
||||||
|
import org.pgpainless.util.TestUtils;
|
||||||
|
|
||||||
|
public class KeyRingValidatorTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRevokedSubkey() throws IOException {
|
||||||
|
String key = "-----BEGIN PGP ARMORED FILE-----\n" +
|
||||||
|
"Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" +
|
||||||
|
"\n" +
|
||||||
|
"xsBNBFpJegABCAC1ePFquP0135m8DYhcybhv7l+ecojitFOd/jRM7hCczIqKgalD\n" +
|
||||||
|
"1Ro1gNr3VmH6FjRIKIvGT+sOzCKne1v3KyAAPoxtwxjkATTKdOGo15I6v5ZjmO1d\n" +
|
||||||
|
"rLQOLSt1TF7XbQSt+ns6PUZWJL907DvECUU5b9FkNUqfQ14QqY+gi7MOyAQez3b7\n" +
|
||||||
|
"Pg5Cyz/kVWQ6TSMW/myDEDEertQ4rDBsptEDFHCC2+iF4hO2LqfiCriu5qyLcKCQ\n" +
|
||||||
|
"pd6dEuwJQ/jjT0D9A9Fwf+i04x6ZPKSU9oNAWqn8OSAq3/0B/hu9V+0U0iHPnJxe\n" +
|
||||||
|
"quykvJk7maxhiGhxBWYXTvDJmoon0NOles7LABEBAAHCwHwEHwEKAA8Fgl4L4QAC\n" +
|
||||||
|
"FQoCmwMCHgEAIQkQaE+tYtwDj7sWIQTy0VCk/piSXVHpFTloT61i3AOPu8ffB/9Q\n" +
|
||||||
|
"60dg60qhA2rPnd/1dCL2B+c8RWnq44PpijE3gA1RQvcRQE5jNzMSo/MnG0mSL5wH\n" +
|
||||||
|
"eTsjSd/DRI3nHP06rs6Qub11NoKhNuya3maz9gyzeZMc/jNib83/BzFCrxsSQm+9\n" +
|
||||||
|
"WHurxXeWXOPMLZs3xS/jG0EDtCJ2Fm4UF19fcIydwN/ssF4NGpfCY82+wTSx4joI\n" +
|
||||||
|
"3cRKObCFJaaBgG5nl+eFr7cfjEIuqCJCaQsXiqBe7d6V3KqN18t+CgSaybMZXcys\n" +
|
||||||
|
"Q/USxEkLhIB2pOZwcz4E3TTFgxRAxcr4cs4Bd2PRz3Z5FKTzo0ma/Ft0UfFJR+fC\n" +
|
||||||
|
"cs55+n6kC9K0y/E7BY2hwsB8BB8BCgAPBYJaSXoAAhUKApsDAh4BACEJEGhPrWLc\n" +
|
||||||
|
"A4+7FiEE8tFQpP6Ykl1R6RU5aE+tYtwDj7uqDQf7BqTD6GNTwXPOt/0kHQPYmbdI\n" +
|
||||||
|
"tX+pWP+o3jaB6VTHDXcn27bttA5M82EXZfae4+bC1dMB+1uLal4ciVgO9ImJC9Nw\n" +
|
||||||
|
"s5fc3JH4R5uuSvpjzjudkJsGu3cAKE3hwiT93Mi6t6ENpLCDSxqxzAmfoOQbVJYW\n" +
|
||||||
|
"Y7gP7Z4Cj0IAP29aprEc0JWoMjHKpKgYF6u0sWgHWBuEXk/6o6GYb2HZYK4ycpY2\n" +
|
||||||
|
"WXKgVhy7/iQDYO1FOfcWQXHVGLn8OzILjobKohNenTT20ZhAASi3LUDSDMTQfxSS\n" +
|
||||||
|
"Vt0nhzWuXJJ4R8PzUVeRJ0A0oMyjZVHivHC6GwMsiQuSUTx8e/GnOByOqfGne80S\n" +
|
||||||
|
"anVsaWV0QGV4YW1wbGUub3JnwsBzBBMBCgAGBYJaSXoAACEJEGhPrWLcA4+7FiEE\n" +
|
||||||
|
"8tFQpP6Ykl1R6RU5aE+tYtwDj7tDfQf+PnxsIFu/0juKBUjjtAYfRzkrrYtMepPj\n" +
|
||||||
|
"taTvGfo1SzUkX/6F/GjdSeVg5Iq6YcBrj8c+cB3EoZpHnScTgWQHwceWQLd9Hhbg\n" +
|
||||||
|
"TrUNvW1eg2CVzN0RBuYMtWu9JM4pH7ssJW1NmN+/N9B67qb2y+JfBwH/la508NzC\n" +
|
||||||
|
"rl3xWTxjT5wNy+FGkNZg23s/0qlO2uxCjc+mRAuAlp5EmTOVWOIBbM0xttjBOx39\n" +
|
||||||
|
"ZmWWQKJZ0nrFjK1jppHqazwWWNX7RHkK81tlbSUtOPoTIJDz38NaiyMcZH3p9okN\n" +
|
||||||
|
"3DU4XtF+oE18M+Z/E0xUQmumbkajFzcUjmd7enozP5BnGESzdNS5Xc7ATQRaSsuA\n" +
|
||||||
|
"AQgAykb8tqlWXtqHGGkBqAq3EnpmvBqrKvqejjtZKAXqEszJ9NlibCGUuLwnNOVO\n" +
|
||||||
|
"R/hcOUlOGH+cyMcApBWJB+7d/83K1eCCdv88nDFVav7hKLKlEBbZJNHgHpJ313pl\n" +
|
||||||
|
"etzCR4x3STEISrEtO71l2HBdrKSYXaxGgILxYwcSi3i2EjzxRDy+0zyy8s7d+OD5\n" +
|
||||||
|
"ShFYexgSrKH3Xx1cxQAJzGGJVx75HHU9GVh3xHwJ7nDm26KzHegG2XPIBXJ2z8vm\n" +
|
||||||
|
"sSVTWyj0AjT4kVVapN0f84AKKjyQ7fguCzXGHFV9jmxDx+YH+9HhjIrHSzbDx6+4\n" +
|
||||||
|
"wyRsxj7Su+hu/bogJ28nnbTzQwARAQABwsCTBCgBCgAmBYJcKq2AHx3IVW5rbm93\n" +
|
||||||
|
"biByZXZvY2F0aW9uIHJlYXNvbiAyMDAAIQkQaE+tYtwDj7sWIQTy0VCk/piSXVHp\n" +
|
||||||
|
"FTloT61i3AOPu6RDCACgqNPoLWPsjWDyZxvF8MyYTB3JivI7RVf8W6mNJTxMDD69\n" +
|
||||||
|
"iWwiC0F6R8M3ljk8vc85C6tQ8iWPVT6cGHhFgQn14a1MYpgyVTTdwjbqvjxmPeyS\n" +
|
||||||
|
"We31yZGz54dAsONnrWScO4ZdKVTtKhu115KELiPmguoN/JwG+OIbgvKvzQX+8D4M\n" +
|
||||||
|
"Gl823A6Ua8/zJm/TAOQolo6X9Sqr9bO1v/z3ecuYkuNeGhQOC3/VQ0TH2xRbmykD\n" +
|
||||||
|
"5XbgffPi0sjg2ZRrDikg/W+40gxW+oHxQ6ZIaIn/OFooj7xooH+jn++f8W8faEk5\n" +
|
||||||
|
"pLOoCwsX0SucDbGvt85D1DhOUD9H0CEkaZbO+113wsGsBBgBCgAJBYJeC+EAApsC\n" +
|
||||||
|
"AVcJEGhPrWLcA4+7wHSgBBkBCgAGBYJeC+EAACEJEEpyNKOhITplFiEEUXksDkji\n" +
|
||||||
|
"/alOk7kRSnI0o6EhOmWnSQgAiu/zdEmHf6Wbwfbs/c6FObfPxGuzLkQr4fZKcqK8\n" +
|
||||||
|
"1MtR1mh1WVLJRgXW4u8cHtZyH5pThngMcUiyzWsa0g6Jaz8w6sr/Wv3e1qdTCITs\n" +
|
||||||
|
"kMrWCDaoDhD2teAjmWuk9u8ZBPJ7xhme+Q/UQ90xomQ/NdCJafirk2Ds92p7N7RK\n" +
|
||||||
|
"SES1KywBhfONJbPw1TdZ9Mts+DGjkucYbe+ZzPxrLpWXur1BSGEqBtTAGW3dS/xp\n" +
|
||||||
|
"wBYNlhasXHjYMr4HeIYYYOx+oR5JgDYoVfp2k0DwK/QXogbja+/Vjv+LrXdNY0t1\n" +
|
||||||
|
"bA35FNnl637M8iCNrXvIoRFARbNyge8c/jSWGPLB/tIyNhYhBPLRUKT+mJJdUekV\n" +
|
||||||
|
"OWhPrWLcA4+7FLwIAK1GngNMnruxWM4EoghKTSmKNrd6p/d3Wsd+y2019A7Nz+4O\n" +
|
||||||
|
"ydkEDvmNVVhlUcfgOf2L6Bf63wdN0ho+ODhCuNSqHe6NL1NhdITbMGnDdKb57IIB\n" +
|
||||||
|
"9CuJFpILn9LZ1Ei6JPEpmpiSEaL+VJt1fMnfc8jtF8N3WcRVfJsq1aslXe8Npg70\n" +
|
||||||
|
"9YVgm2OXsNWgktl9fciu4ENTybQGjpN9WTa1aU1nkko6NUoIfjtM+PO4VU7x00M+\n" +
|
||||||
|
"dTJsYGhnc96EtT8EfSAIFBKZRAkMBFhEcdkxa8hCKI3+nyI3gTq0TcFST3wy05Am\n" +
|
||||||
|
"oV7wlgzUAMsW7MV2NpG7fJul2Q7puKw+udBUc0TCwawEGAEKAAkFglro/4ACmwIB\n" +
|
||||||
|
"VwkQaE+tYtwDj7vAdKAEGQEKAAYFglro/4AAIQkQSnI0o6EhOmUWIQRReSwOSOL9\n" +
|
||||||
|
"qU6TuRFKcjSjoSE6ZeFHB/92jhUTXrEgho6DYhmVFuXa3NGhAjIyZo3yYHMoL9aZ\n" +
|
||||||
|
"3DUyjxhAyRDpI2CrahQ4JsPhej2m+3fHWa34/tb5mpHYFWEahQvdWSFCcU7p2NUK\n" +
|
||||||
|
"cq2zNA6ixO2+fQQhmbrYR+TFxYmhLjCGUNt14E/XaIL1VxPQOA5KbiRPpa8BsUNl\n" +
|
||||||
|
"Nik9ASPWyn0ZA0rjJ1ZV7nJarXVbuZDEcUDuDm3cA5tup7juB8fTz2BDcg3Ka+Oc\n" +
|
||||||
|
"PEz0GgZfq9K40di3r9IHLBhNPHieFVIj9j/JyMnTvVOceM3J/Rb0MCWJVbXNBKpR\n" +
|
||||||
|
"MDibCQh+7fbqyQEM/zIpmk0TgBpTZZqMP0gxYdWImT1IFiEE8tFQpP6Ykl1R6RU5\n" +
|
||||||
|
"aE+tYtwDj7tOtggAhgAqvOB142L2SkS3ZIdwuhAtWLPHCtEwBOqGtP8Z204rqAmb\n" +
|
||||||
|
"nJymzo77+OT+SScnDTrwzOUJnCi0qPUxfuxhvHxnBxBIjaoMcF++iKsqF1vf6WuX\n" +
|
||||||
|
"OjbJ1N8I08pB2niht5MxIZ9rMGDeASj79X7I9Jjzsd30OVGfTZyy3VyYPxcJ6n/s\n" +
|
||||||
|
"ZocNmaTv0/F8K3TirSH6JDXdY5zirRi99GJ3R+AL6OzxrChuvLFSEtIRJrW5XVfg\n" +
|
||||||
|
"3whc0XD+5J9RsHoL33ub9ZhQHFKsjrf0nGYbEFwMhSdysfTYYMbwKi0CcQeQtPP0\n" +
|
||||||
|
"Y87zSryajDMFXQS0exdvhN4AXDlPlB3Rrkj7CQ==\n" +
|
||||||
|
"=yTKS\n" +
|
||||||
|
"-----END PGP ARMORED FILE-----\n";
|
||||||
|
|
||||||
|
PGPPublicKeyRing keyRing = PGPainless.readKeyRing().publicKeyRing(key);
|
||||||
|
PGPPublicKeyRing validated = KeyRingValidator.validate(keyRing, PGPainless.getPolicy());
|
||||||
|
|
||||||
|
Iterator<PGPPublicKey> keys = validated.getPublicKeys();
|
||||||
|
assertFalse(keys.next().hasRevocation());
|
||||||
|
assertTrue(keys.next().hasRevocation());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void badSignatureTest() throws IOException {
|
||||||
|
String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
|
||||||
|
"\n" +
|
||||||
|
"xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" +
|
||||||
|
"/seOXpgecTdOcVttfzC8ycIKrt3aQTiwOG/ctaR4Bk/t6ayNFfdUNxHWk4WCKzdz\n" +
|
||||||
|
"/56fW2O0F23qIRd8UUJp5IIlN4RDdRCtdhVQIAuzvp2oVy/LaS2kxQoKvph/5pQ/\n" +
|
||||||
|
"5whqsyroEWDJoSV0yOb25B/iwk/pLUFoyhDG9bj0kIzDxrEqW+7Ba8nocQlecMF3\n" +
|
||||||
|
"X5KMN5kp2zraLv9dlBBpWW43XktjcCZgMy20SouraVma8Je/ECwUWYUiAZxLIlMv\n" +
|
||||||
|
"9CurEOtxUw6N3RdOtLmYZS9uEnn5y1UkF88o8Nku890uk6BrewFzJyLAx5wRZ4F0\n" +
|
||||||
|
"qV/yq36UWQ0JB/AUGhHVPdFf6pl6eaxBwT5GXvbBUibtf8YI2og5RsgTWtXfU7eb\n" +
|
||||||
|
"SGXrl5ZMpbA6mbfhd0R8aPxWfmDWiIOhBufhMCvUHh1sApMKVZnvIff9/0Dca3wb\n" +
|
||||||
|
"vLIwa3T4CyshfT0AEQEAAc0hQm9iIEJhYmJhZ2UgPGJvYkBvcGVucGdwLmV4YW1w\n" +
|
||||||
|
"bGU+wsEOBBMBCgA4AhsDBQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAFiEE0aZuGiOx\n" +
|
||||||
|
"gsmYD3iM+/zIKgFeczAFAl2lnvoACgkQ+/zIKgFeczBvbAv/VNk90a6hG8Od9xTz\n" +
|
||||||
|
"XxH5YRFUSGfIA1yjPIVOnKqhMwps2U+sWE3urL+MvjyQRlyRV8oY9IOhQ5Esm6DO\n" +
|
||||||
|
"ZYrTnE7qVETm1ajIAP2OFChEc55uH88x/anpPOXOJY7S8jbn3naC9qad75BrZ+3g\n" +
|
||||||
|
"9EBUWiy5p8TykP05WSnSxNRt7vFKLfEB4nGkehpwHXOVF0CRNwYle42bg8lpmdXF\n" +
|
||||||
|
"DcCZCi+qEbafmTQzkAqyzS3nCh3IAqq6Y0kBuaKLm2tSNUOlZbD+OHYQNZ5Jix7c\n" +
|
||||||
|
"ZUzs6Xh4+I55NRWl5smrLq66yOQoFPy9jot/Qxikx/wP3MsAzeGaZSEPc0fHp5G1\n" +
|
||||||
|
"6rlGbxQ3vl8/usUV7W+TMEMljgwd5x8POR6HC8EaCDfVnUBCPi/Gv+egLjsIbPJZ\n" +
|
||||||
|
"ZEroiE40e6/UoCiQtlpQB5exPJYSd1Q1txCwueih99PHepsDhmUQKiACszNU+RRo\n" +
|
||||||
|
"zAYau2VdHqnRJ7QYdxHDiH49jPK4NTMyb/tJh2TiIwcmsIpGzsDNBF2lnPIBDADW\n" +
|
||||||
|
"ML9cbGMrp12CtF9b2P6z9TTT74S8iyBOzaSvdGDQY/sUtZXRg21HWamXnn9sSXvI\n" +
|
||||||
|
"DEINOQ6A9QxdxoqWdCHrOuW3ofneYXoG+zeKc4dC86wa1TR2q9vW+RMXSO4uImA+\n" +
|
||||||
|
"Uzula/6k1DogDf28qhCxMwG/i/m9g1c/0aApuDyKdQ1PXsHHNlgd/Dn6rrd5y2AO\n" +
|
||||||
|
"baifV7wIhEJnvqgFXDN2RXGjLeCOHV4Q2WTYPg/S4k1nMXVDwZXrvIsA0YwIMgIT\n" +
|
||||||
|
"86Rafp1qKlgPNbiIlC1g9RY/iFaGN2b4Ir6GDohBQSfZW2+LXoPZuVE/wGlQ01rh\n" +
|
||||||
|
"827KVZW4lXvqsge+wtnWlszcselGATyzqOK9LdHPdZGzROZYI2e8c+paLNDdVPL6\n" +
|
||||||
|
"vdRBUnkCaEkOtl1mr2JpQi5nTU+gTX4IeInC7E+1a9UDF/Y85ybUz8XV8rUnR76U\n" +
|
||||||
|
"qVC7KidNepdHbZjjXCt8/Zo+Tec9JNbYNQB/e9ExmDntmlHEsSEQzFwzj8sxH48A\n" +
|
||||||
|
"EQEAAcLA9gQYAQoAIBYhBNGmbhojsYLJmA94jPv8yCoBXnMwBQJdpZzyAhsMAAoJ\n" +
|
||||||
|
"EPv8yCoBXnMw6f8L/26C34dkjBffTzMj5Bdzm8MtF67OYneJ4TQMw7+41IL4rVcS\n" +
|
||||||
|
"KhIhk/3Ud5knaRtP2ef1+5F66h9/RPQOJ5+tvBwhBAcUWSupKnUrdVaZQanYmtSx\n" +
|
||||||
|
"cVV2PL9+QEiNN3tzluhaWO//rACxJ+K/ZXQlIzwQVTpNhfGzAaMVV9zpf3u0k14i\n" +
|
||||||
|
"tcv6alKY8+rLZvO1wIIeRZLmU0tZDD5HtWDvUV7rIFI1WuoLb+KZgbYn3OWjCPHV\n" +
|
||||||
|
"dTrdZ2CqnZbG3SXw6awH9bzRLV9EXkbhIMez0deCVdeo+wFFklh8/5VK2b0vk/+w\n" +
|
||||||
|
"qMJxfpa1lHvJLobzOP9fvrswsr92MA2+k901WeISR7qEzcI0Fdg8AyFAExaEK6Vy\n" +
|
||||||
|
"jP7SXGLwvfisw34OxuZr3qmx1Sufu4toH3XrB7QJN8XyqqbsGxUCBqWif9RSK4xj\n" +
|
||||||
|
"zRTe56iPeiSJJOIciMP9i2ldI+KgLycyeDvGoBj0HCLO3gVaBe4ubVrj5KjhX2PV\n" +
|
||||||
|
"NEJd3XZRzaXZE2aAMcLA9gQYAQoAIBYhBNGmbhojsYLJmA94jPv8yCoBXnMwBQJd\n" +
|
||||||
|
"pZzyAhsMAAoJEPv8yCoBXnMw6f8L/26C34dkjBffTzMj5Bdzm8MtF67OYneJ4TQM\n" +
|
||||||
|
"w7+41IL4rVcSKhIhk/3Ud5knaRtP2ef1+5F66h9/RPQOJ5+tvBwhBAcUWSupKnUr\n" +
|
||||||
|
"dVaZQanYmtSxcVV2PL9+QEiNN3tzluhaWO//rACxJ+K/ZXQlIzwQVTpNhfGzAaMV\n" +
|
||||||
|
"V9zpf3u0k14itcv6alKY8+rLZvO1wIIeRZLmU0tZDD5HtWDvUV7rIFI1WuoLb+KZ\n" +
|
||||||
|
"gbYn3OWjCPHVdTrdZ2CqnZbG3SXw6awH9bzRLV9EXkbhIMez0deCVdeo+wFFklh8\n" +
|
||||||
|
"/5VK2b0vk/+wqMJxfpa1lHvJLobzOP9fvrswsr92MA2+k901WeISR7qEzcI0Fdg8\n" +
|
||||||
|
"AyFAExaEK6VyjP7SXGLwvfisw34OxuZr3qmx1Sufu4toH3XrB7QJN8XyqqbsGxUC\n" +
|
||||||
|
"BqWif9RSK4xjzRTe56iPeiSJJOIciMP9i2ldI+KgLycyeDvGoBj0HCLO3gVaBe4u\n" +
|
||||||
|
"bVrj5KjhX2PVNEJd3XZRzaXZE2Z/MQ==\n" +
|
||||||
|
"=6+l9\n" +
|
||||||
|
"-----END PGP PUBLIC KEY BLOCK-----\n";
|
||||||
|
PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(key);
|
||||||
|
PGPPublicKeyRing validated = KeyRingValidator.validate(publicKeys, PGPainless.getPolicy());
|
||||||
|
// CHECKSTYLE:OFF
|
||||||
|
System.out.println(ArmorUtils.toAsciiArmoredString(validated));
|
||||||
|
// CHECKSTYLE:ON
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void unboundSubkey() throws IOException {
|
||||||
|
String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
|
||||||
|
"\n" +
|
||||||
|
"xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" +
|
||||||
|
"/seOXpgecTdOcVttfzC8ycIKrt3aQTiwOG/ctaR4Bk/t6ayNFfdUNxHWk4WCKzdz\n" +
|
||||||
|
"/56fW2O0F23qIRd8UUJp5IIlN4RDdRCtdhVQIAuzvp2oVy/LaS2kxQoKvph/5pQ/\n" +
|
||||||
|
"5whqsyroEWDJoSV0yOb25B/iwk/pLUFoyhDG9bj0kIzDxrEqW+7Ba8nocQlecMF3\n" +
|
||||||
|
"X5KMN5kp2zraLv9dlBBpWW43XktjcCZgMy20SouraVma8Je/ECwUWYUiAZxLIlMv\n" +
|
||||||
|
"9CurEOtxUw6N3RdOtLmYZS9uEnn5y1UkF88o8Nku890uk6BrewFzJyLAx5wRZ4F0\n" +
|
||||||
|
"qV/yq36UWQ0JB/AUGhHVPdFf6pl6eaxBwT5GXvbBUibtf8YI2og5RsgTWtXfU7eb\n" +
|
||||||
|
"SGXrl5ZMpbA6mbfhd0R8aPxWfmDWiIOhBufhMCvUHh1sApMKVZnvIff9/0Dca3wb\n" +
|
||||||
|
"vLIwa3T4CyshfT0AEQEAAc0hQm9iIEJhYmJhZ2UgPGJvYkBvcGVucGdwLmV4YW1w\n" +
|
||||||
|
"bGU+wsEOBBMBCgA4AhsDBQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAFiEE0aZuGiOx\n" +
|
||||||
|
"gsmYD3iM+/zIKgFeczAFAl2lnvoACgkQ+/zIKgFeczBvbAv/VNk90a6hG8Od9xTz\n" +
|
||||||
|
"XxH5YRFUSGfIA1yjPIVOnKqhMwps2U+sWE3urL+MvjyQRlyRV8oY9IOhQ5Esm6DO\n" +
|
||||||
|
"ZYrTnE7qVETm1ajIAP2OFChEc55uH88x/anpPOXOJY7S8jbn3naC9qad75BrZ+3g\n" +
|
||||||
|
"9EBUWiy5p8TykP05WSnSxNRt7vFKLfEB4nGkehpwHXOVF0CRNwYle42bg8lpmdXF\n" +
|
||||||
|
"DcCZCi+qEbafmTQzkAqyzS3nCh3IAqq6Y0kBuaKLm2tSNUOlZbD+OHYQNZ5Jix7c\n" +
|
||||||
|
"ZUzs6Xh4+I55NRWl5smrLq66yOQoFPy9jot/Qxikx/wP3MsAzeGaZSEPc0fHp5G1\n" +
|
||||||
|
"6rlGbxQ3vl8/usUV7W+TMEMljgwd5x8POR6HC8EaCDfVnUBCPi/Gv+egLjsIbPJZ\n" +
|
||||||
|
"ZEroiE40e6/UoCiQtlpQB5exPJYSd1Q1txCwueih99PHepsDhmUQKiACszNU+RRo\n" +
|
||||||
|
"zAYau2VdHqnRJ7QYdxHDiH49jPK4NTMyb/tJh2TiIwcmsIpGzsDNBF2lnPIBDADW\n" +
|
||||||
|
"ML9cbGMrp12CtF9b2P6z9TTT74S8iyBOzaSvdGDQY/sUtZXRg21HWamXnn9sSXvI\n" +
|
||||||
|
"DEINOQ6A9QxdxoqWdCHrOuW3ofneYXoG+zeKc4dC86wa1TR2q9vW+RMXSO4uImA+\n" +
|
||||||
|
"Uzula/6k1DogDf28qhCxMwG/i/m9g1c/0aApuDyKdQ1PXsHHNlgd/Dn6rrd5y2AO\n" +
|
||||||
|
"baifV7wIhEJnvqgFXDN2RXGjLeCOHV4Q2WTYPg/S4k1nMXVDwZXrvIsA0YwIMgIT\n" +
|
||||||
|
"86Rafp1qKlgPNbiIlC1g9RY/iFaGN2b4Ir6GDohBQSfZW2+LXoPZuVE/wGlQ01rh\n" +
|
||||||
|
"827KVZW4lXvqsge+wtnWlszcselGATyzqOK9LdHPdZGzROZYI2e8c+paLNDdVPL6\n" +
|
||||||
|
"vdRBUnkCaEkOtl1mr2JpQi5nTU+gTX4IeInC7E+1a9UDF/Y85ybUz8XV8rUnR76U\n" +
|
||||||
|
"qVC7KidNepdHbZjjXCt8/Zo+Tec9JNbYNQB/e9ExmDntmlHEsSEQzFwzj8sxH48A\n" +
|
||||||
|
"EQEAAcLA9gQYAQoAIBYhBNGmbhojsYLJmA94jPv8yCoBXnMwBQJdpZzyAhsMAAoJ\n" +
|
||||||
|
"EPv8yCoBXnMw6f8L/26C34dkjBffTzMj5Bdzm8MtF67OYneJ4TQMw7+41IL4rVcS\n" +
|
||||||
|
"KhIhk/3Ud5knaRtP2ef1+5F66h9/RPQOJ5+tvBwhBAcUWSupKnUrdVaZQanYmtSx\n" +
|
||||||
|
"cVV2PL9+QEiNN3tzluhaWO//rACxJ+K/ZXQlIzwQVTpNhfGzAaMVV9zpf3u0k14i\n" +
|
||||||
|
"tcv6alKY8+rLZvO1wIIeRZLmU0tZDD5HtWDvUV7rIFI1WuoLb+KZgbYn3OWjCPHV\n" +
|
||||||
|
"dTrdZ2CqnZbG3SXw6awH9bzRLV9EXkbhIMez0deCVdeo+wFFklh8/5VK2b0vk/+w\n" +
|
||||||
|
"qMJxfpa1lHvJLobzOP9fvrswsr92MA2+k901WeISR7qEzcI0Fdg8AyFAExaEK6Vy\n" +
|
||||||
|
"jP7SXGLwvfisw34OxuZr3qmx1Sufu4toH3XrB7QJN8XyqqbsGxUCBqWif9RSK4xj\n" +
|
||||||
|
"zRTe56iPeiSJJOIciMP9i2ldI+KgLycyeDvGoBj0HCLO3gVaBe4ubVrj5KjhX2PV\n" +
|
||||||
|
"NEJd3XZRzaXZE2aAMc7ATQRgSLpPAQgAx2jWKrOk6fGy2/KJGTs6vAN8c+fg+PgH\n" +
|
||||||
|
"6xDkasqmGllG0xPVOTML+Ge3i025IezFp1BNApPLWVksFRnbTF/Aiwbpeax7mub0\n" +
|
||||||
|
"PdFo4LeNxfUZhl/83+aZKYvT/j9AB7rjILhu+wqZmLY9UAkdvIO0SfEUIFf0mL5c\n" +
|
||||||
|
"9UJm47IOpY0EPc8l7B5DkXpkA63BKGyMPle6XZV3r/VIltnMnQezY1TErjeEnFrE\n" +
|
||||||
|
"KYxqMgDhPIEaBSK8tqf3POwY2mP42K8+yke/St9+FvLIAKOj2KpVp/0pxcNBBoHA\n" +
|
||||||
|
"9oo0W4CQP6S0hQkFZy9iZ1/NIpU+YLy8miBpdTMYm4CZLz5mrT2mpwARAQAB\n" +
|
||||||
|
"=T4QR\n" +
|
||||||
|
"-----END PGP PUBLIC KEY BLOCK-----\n";
|
||||||
|
|
||||||
|
PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(key);
|
||||||
|
PGPPublicKey unbound = CollectionUtils.iteratorToList(publicKeys.getPublicKeys()).get(2);
|
||||||
|
assertNotNull(unbound);
|
||||||
|
|
||||||
|
Date validationDate = TestUtils.getUTCDate("2019-10-15 10:18:26 UTC");
|
||||||
|
KeyRingInfo info = new KeyRingInfo(publicKeys, validationDate);
|
||||||
|
for (PGPPublicKey publicKey : publicKeys) {
|
||||||
|
if (publicKey != unbound) {
|
||||||
|
assertTrue(info.isKeyValidlyBound(publicKey.getKeyID()));
|
||||||
|
} else {
|
||||||
|
assertFalse(info.isKeyValidlyBound(publicKey.getKeyID()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void expired() throws IOException {
|
||||||
|
String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
|
||||||
|
"\n" +
|
||||||
|
"xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" +
|
||||||
|
"/seOXpgecTdOcVttfzC8ycIKrt3aQTiwOG/ctaR4Bk/t6ayNFfdUNxHWk4WCKzdz\n" +
|
||||||
|
"/56fW2O0F23qIRd8UUJp5IIlN4RDdRCtdhVQIAuzvp2oVy/LaS2kxQoKvph/5pQ/\n" +
|
||||||
|
"5whqsyroEWDJoSV0yOb25B/iwk/pLUFoyhDG9bj0kIzDxrEqW+7Ba8nocQlecMF3\n" +
|
||||||
|
"X5KMN5kp2zraLv9dlBBpWW43XktjcCZgMy20SouraVma8Je/ECwUWYUiAZxLIlMv\n" +
|
||||||
|
"9CurEOtxUw6N3RdOtLmYZS9uEnn5y1UkF88o8Nku890uk6BrewFzJyLAx5wRZ4F0\n" +
|
||||||
|
"qV/yq36UWQ0JB/AUGhHVPdFf6pl6eaxBwT5GXvbBUibtf8YI2og5RsgTWtXfU7eb\n" +
|
||||||
|
"SGXrl5ZMpbA6mbfhd0R8aPxWfmDWiIOhBufhMCvUHh1sApMKVZnvIff9/0Dca3wb\n" +
|
||||||
|
"vLIwa3T4CyshfT0AEQEAAc0hQm9iIEJhYmJhZ2UgPGJvYkBvcGVucGdwLmV4YW1w\n" +
|
||||||
|
"bGU+wsFcBBMBCgCQBYJgSLnzBYkCH0c9BQsJCAcCCRD7/MgqAV5zMEcUAAAAAAAe\n" +
|
||||||
|
"ACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmcwVhGjJD1hkSHawAIfkCGs\n" +
|
||||||
|
"HrkFeok37qxAtN/xGj08tAYVCgkICwIEFgIDAQIXgAIbAwIeARYhBNGmbhojsYLJ\n" +
|
||||||
|
"mA94jPv8yCoBXnMwAABJmgwAh3SdjziuXu5K4slejN57yezIZBG92CCEfqdoFOE/\n" +
|
||||||
|
"LShjMkZbRZEjOADmwTUevAVNRzBtU6SesOE3lL+sHsdmwcQACEbQXvT6AaDQnkyT\n" +
|
||||||
|
"N/Kse4reDLA+Cwdvy+dKdIF5g1IKzLc5gSSHHlGi0dc4kTQYXicXl4rw6y4fgfx8\n" +
|
||||||
|
"6wWf9ujUexjI35X1A3+yGVkB12lDC4XxcIuQjd2PnxsrRIk8ty32qtv+4Ww3YrvA\n" +
|
||||||
|
"wsY7ft9YkMRs7kJ7joVuCWbzje/mpYOSc7t3TCx0VgkRtcXewyGQ22977Vkdk+gi\n" +
|
||||||
|
"zmw/f/fV+s1fPzhLYonlmiWwU7COF9dDkuEh2NOkAcuZxVZ/QjMZ449M8kBgCLcD\n" +
|
||||||
|
"JGrEzIseP9vW8EHRNGxOZx/0Bo0HPMSlUesOugsoIVXBop/ixtd1eD5ijQt6HhvW\n" +
|
||||||
|
"CgASMtfpA4DT9boeGRYXH4vySDqoHPVkKDKYqDHZ526Z98M1a/76njOLVgioIOL/\n" +
|
||||||
|
"gND3vo4iOAfwfoQIvi8b/B0fzsDNBF2lnPIBDADWML9cbGMrp12CtF9b2P6z9TTT\n" +
|
||||||
|
"74S8iyBOzaSvdGDQY/sUtZXRg21HWamXnn9sSXvIDEINOQ6A9QxdxoqWdCHrOuW3\n" +
|
||||||
|
"ofneYXoG+zeKc4dC86wa1TR2q9vW+RMXSO4uImA+Uzula/6k1DogDf28qhCxMwG/\n" +
|
||||||
|
"i/m9g1c/0aApuDyKdQ1PXsHHNlgd/Dn6rrd5y2AObaifV7wIhEJnvqgFXDN2RXGj\n" +
|
||||||
|
"LeCOHV4Q2WTYPg/S4k1nMXVDwZXrvIsA0YwIMgIT86Rafp1qKlgPNbiIlC1g9RY/\n" +
|
||||||
|
"iFaGN2b4Ir6GDohBQSfZW2+LXoPZuVE/wGlQ01rh827KVZW4lXvqsge+wtnWlszc\n" +
|
||||||
|
"selGATyzqOK9LdHPdZGzROZYI2e8c+paLNDdVPL6vdRBUnkCaEkOtl1mr2JpQi5n\n" +
|
||||||
|
"TU+gTX4IeInC7E+1a9UDF/Y85ybUz8XV8rUnR76UqVC7KidNepdHbZjjXCt8/Zo+\n" +
|
||||||
|
"Tec9JNbYNQB/e9ExmDntmlHEsSEQzFwzj8sxH48AEQEAAcLA9gQYAQoAIBYhBNGm\n" +
|
||||||
|
"bhojsYLJmA94jPv8yCoBXnMwBQJdpZzyAhsMAAoJEPv8yCoBXnMw6f8L/26C34dk\n" +
|
||||||
|
"jBffTzMj5Bdzm8MtF67OYneJ4TQMw7+41IL4rVcSKhIhk/3Ud5knaRtP2ef1+5F6\n" +
|
||||||
|
"6h9/RPQOJ5+tvBwhBAcUWSupKnUrdVaZQanYmtSxcVV2PL9+QEiNN3tzluhaWO//\n" +
|
||||||
|
"rACxJ+K/ZXQlIzwQVTpNhfGzAaMVV9zpf3u0k14itcv6alKY8+rLZvO1wIIeRZLm\n" +
|
||||||
|
"U0tZDD5HtWDvUV7rIFI1WuoLb+KZgbYn3OWjCPHVdTrdZ2CqnZbG3SXw6awH9bzR\n" +
|
||||||
|
"LV9EXkbhIMez0deCVdeo+wFFklh8/5VK2b0vk/+wqMJxfpa1lHvJLobzOP9fvrsw\n" +
|
||||||
|
"sr92MA2+k901WeISR7qEzcI0Fdg8AyFAExaEK6VyjP7SXGLwvfisw34OxuZr3qmx\n" +
|
||||||
|
"1Sufu4toH3XrB7QJN8XyqqbsGxUCBqWif9RSK4xjzRTe56iPeiSJJOIciMP9i2ld\n" +
|
||||||
|
"I+KgLycyeDvGoBj0HCLO3gVaBe4ubVrj5KjhX2PVNEJd3XZRzaXZE2aAMQ==\n" +
|
||||||
|
"=LxAY\n" +
|
||||||
|
"-----END PGP PUBLIC KEY BLOCK-----\n";
|
||||||
|
|
||||||
|
PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(key);
|
||||||
|
PGPPublicKeyRing validated = KeyRingValidator.validate(publicKeys, PGPainless.getPolicy());
|
||||||
|
}
|
||||||
|
}
|
|
@ -61,7 +61,7 @@ public class GenerateKeyWithAdditionalUserIdTest {
|
||||||
.build();
|
.build();
|
||||||
PGPPublicKeyRing publicKeys = KeyRingUtils.publicKeyRingFrom(secretKeys);
|
PGPPublicKeyRing publicKeys = KeyRingUtils.publicKeyRingFrom(secretKeys);
|
||||||
|
|
||||||
JUtils.assertEquals(expiration.getTime(), PGPainless.inspectKeyRing(publicKeys).getExpirationDate().getTime(),2000);
|
JUtils.assertEquals(expiration.getTime(), PGPainless.inspectKeyRing(publicKeys).getPrimaryKeyExpirationDate().getTime(),2000);
|
||||||
|
|
||||||
Iterator<String> userIds = publicKeys.getPublicKey().getUserIDs();
|
Iterator<String> userIds = publicKeys.getPublicKey().getUserIDs();
|
||||||
assertEquals("primary@user.id", userIds.next());
|
assertEquals("primary@user.id", userIds.next());
|
||||||
|
|
|
@ -34,6 +34,7 @@ import org.pgpainless.algorithm.PublicKeyAlgorithm;
|
||||||
import org.pgpainless.key.TestKeys;
|
import org.pgpainless.key.TestKeys;
|
||||||
import org.pgpainless.key.protection.UnprotectedKeysProtector;
|
import org.pgpainless.key.protection.UnprotectedKeysProtector;
|
||||||
import org.pgpainless.key.util.KeyRingUtils;
|
import org.pgpainless.key.util.KeyRingUtils;
|
||||||
|
import org.pgpainless.util.ArmorUtils;
|
||||||
import org.pgpainless.util.Passphrase;
|
import org.pgpainless.util.Passphrase;
|
||||||
|
|
||||||
public class KeyRingInfoTest {
|
public class KeyRingInfoTest {
|
||||||
|
@ -67,8 +68,8 @@ public class KeyRingInfoTest {
|
||||||
|
|
||||||
assertEquals(TestKeys.EMIL_CREATION_DATE, sInfo.getCreationDate());
|
assertEquals(TestKeys.EMIL_CREATION_DATE, sInfo.getCreationDate());
|
||||||
assertEquals(TestKeys.EMIL_CREATION_DATE, pInfo.getCreationDate());
|
assertEquals(TestKeys.EMIL_CREATION_DATE, pInfo.getCreationDate());
|
||||||
assertNull(sInfo.getExpirationDate());
|
assertNull(sInfo.getPrimaryKeyExpirationDate());
|
||||||
assertNull(pInfo.getExpirationDate());
|
assertNull(pInfo.getPrimaryKeyExpirationDate());
|
||||||
assertEquals(TestKeys.EMIL_CREATION_DATE.getTime(), sInfo.getLastModified().getTime(), 50);
|
assertEquals(TestKeys.EMIL_CREATION_DATE.getTime(), sInfo.getLastModified().getTime(), 50);
|
||||||
assertEquals(TestKeys.EMIL_CREATION_DATE.getTime(), pInfo.getLastModified().getTime(), 50);
|
assertEquals(TestKeys.EMIL_CREATION_DATE.getTime(), pInfo.getLastModified().getTime(), 50);
|
||||||
|
|
||||||
|
@ -76,6 +77,9 @@ public class KeyRingInfoTest {
|
||||||
assertNull(pInfo.getRevocationDate());
|
assertNull(pInfo.getRevocationDate());
|
||||||
Date revocationDate = new Date();
|
Date revocationDate = new Date();
|
||||||
PGPSecretKeyRing revoked = PGPainless.modifyKeyRing(secretKeys).revoke(new UnprotectedKeysProtector()).done();
|
PGPSecretKeyRing revoked = PGPainless.modifyKeyRing(secretKeys).revoke(new UnprotectedKeysProtector()).done();
|
||||||
|
// CHECKSTYLE:OFF
|
||||||
|
System.out.println(ArmorUtils.toAsciiArmoredString(revoked));
|
||||||
|
// CHECKSTYLE:ON
|
||||||
KeyRingInfo rInfo = PGPainless.inspectKeyRing(revoked);
|
KeyRingInfo rInfo = PGPainless.inspectKeyRing(revoked);
|
||||||
assertNotNull(rInfo.getRevocationDate());
|
assertNotNull(rInfo.getRevocationDate());
|
||||||
assertEquals(revocationDate.getTime(), rInfo.getRevocationDate().getTime(), 1000);
|
assertEquals(revocationDate.getTime(), rInfo.getRevocationDate().getTime(), 1000);
|
||||||
|
|
|
@ -45,7 +45,7 @@ import org.pgpainless.key.protection.PasswordBasedSecretKeyRingProtector;
|
||||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||||
import org.pgpainless.key.protection.UnprotectedKeysProtector;
|
import org.pgpainless.key.protection.UnprotectedKeysProtector;
|
||||||
import org.pgpainless.key.util.RevocationAttributes;
|
import org.pgpainless.key.util.RevocationAttributes;
|
||||||
import org.pgpainless.key.util.SignatureUtils;
|
import org.pgpainless.signature.SignatureUtils;
|
||||||
|
|
||||||
public class UserIdRevocationTest {
|
public class UserIdRevocationTest {
|
||||||
|
|
||||||
|
|
|
@ -45,17 +45,17 @@ public class ChangeExpirationTest {
|
||||||
PGPSecretKeyRing secretKeys = TestKeys.getEmilSecretKeyRing();
|
PGPSecretKeyRing secretKeys = TestKeys.getEmilSecretKeyRing();
|
||||||
KeyRingInfo sInfo = PGPainless.inspectKeyRing(secretKeys);
|
KeyRingInfo sInfo = PGPainless.inspectKeyRing(secretKeys);
|
||||||
|
|
||||||
assertNull(sInfo.getExpirationDate());
|
assertNull(sInfo.getPrimaryKeyExpirationDate());
|
||||||
assertNull(sInfo.getExpirationDate(subKeyFingerprint));
|
assertNull(sInfo.getSubkeyExpirationDate(subKeyFingerprint));
|
||||||
|
|
||||||
Date date = new Date(1606493432000L);
|
Date date = new Date(1606493432000L);
|
||||||
secretKeys = PGPainless.modifyKeyRing(secretKeys)
|
secretKeys = PGPainless.modifyKeyRing(secretKeys)
|
||||||
.setExpirationDate(date, new UnprotectedKeysProtector()).done();
|
.setExpirationDate(date, new UnprotectedKeysProtector()).done();
|
||||||
sInfo = PGPainless.inspectKeyRing(secretKeys);
|
sInfo = PGPainless.inspectKeyRing(secretKeys);
|
||||||
assertNotNull(sInfo.getExpirationDate());
|
assertNotNull(sInfo.getPrimaryKeyExpirationDate());
|
||||||
assertEquals(date.getTime(), sInfo.getExpirationDate().getTime());
|
assertEquals(date.getTime(), sInfo.getPrimaryKeyExpirationDate().getTime());
|
||||||
// subkey unchanged
|
// subkey unchanged
|
||||||
assertNull(sInfo.getExpirationDate(subKeyFingerprint));
|
assertNull(sInfo.getSubkeyExpirationDate(subKeyFingerprint));
|
||||||
|
|
||||||
// We need to wait for one second as OpenPGP signatures have coarse-grained (up to a second)
|
// We need to wait for one second as OpenPGP signatures have coarse-grained (up to a second)
|
||||||
// accuracy. Creating two signatures within a short amount of time will make the second one
|
// accuracy. Creating two signatures within a short amount of time will make the second one
|
||||||
|
@ -66,8 +66,8 @@ public class ChangeExpirationTest {
|
||||||
.setExpirationDate(null, new UnprotectedKeysProtector()).done();
|
.setExpirationDate(null, new UnprotectedKeysProtector()).done();
|
||||||
|
|
||||||
sInfo = PGPainless.inspectKeyRing(secretKeys);
|
sInfo = PGPainless.inspectKeyRing(secretKeys);
|
||||||
assertNull(sInfo.getExpirationDate());
|
assertNull(sInfo.getPrimaryKeyExpirationDate());
|
||||||
assertNull(sInfo.getExpirationDate(subKeyFingerprint));
|
assertNull(sInfo.getSubkeyExpirationDate(subKeyFingerprint));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -75,16 +75,16 @@ public class ChangeExpirationTest {
|
||||||
PGPSecretKeyRing secretKeys = TestKeys.getEmilSecretKeyRing();
|
PGPSecretKeyRing secretKeys = TestKeys.getEmilSecretKeyRing();
|
||||||
KeyRingInfo sInfo = PGPainless.inspectKeyRing(secretKeys);
|
KeyRingInfo sInfo = PGPainless.inspectKeyRing(secretKeys);
|
||||||
|
|
||||||
assertNull(sInfo.getExpirationDate(subKeyFingerprint));
|
assertNull(sInfo.getSubkeyExpirationDate(subKeyFingerprint));
|
||||||
assertNull(sInfo.getExpirationDate());
|
assertNull(sInfo.getPrimaryKeyExpirationDate());
|
||||||
|
|
||||||
Date date = new Date(1606493432000L);
|
Date date = new Date(1606493432000L);
|
||||||
secretKeys = PGPainless.modifyKeyRing(secretKeys)
|
secretKeys = PGPainless.modifyKeyRing(secretKeys)
|
||||||
.setExpirationDate(subKeyFingerprint, date, new UnprotectedKeysProtector()).done();
|
.setExpirationDate(subKeyFingerprint, date, new UnprotectedKeysProtector()).done();
|
||||||
sInfo = PGPainless.inspectKeyRing(secretKeys);
|
sInfo = PGPainless.inspectKeyRing(secretKeys);
|
||||||
assertNotNull(sInfo.getExpirationDate(subKeyFingerprint));
|
assertNotNull(sInfo.getSubkeyExpirationDate(subKeyFingerprint));
|
||||||
assertEquals(date.getTime(), sInfo.getExpirationDate(subKeyFingerprint).getTime());
|
assertEquals(date.getTime(), sInfo.getSubkeyExpirationDate(subKeyFingerprint).getTime());
|
||||||
assertNull(sInfo.getExpirationDate());
|
assertNull(sInfo.getPrimaryKeyExpirationDate());
|
||||||
|
|
||||||
// We need to wait for one second as OpenPGP signatures have coarse-grained (up to a second)
|
// We need to wait for one second as OpenPGP signatures have coarse-grained (up to a second)
|
||||||
// accuracy. Creating two signatures within a short amount of time will make the second one
|
// accuracy. Creating two signatures within a short amount of time will make the second one
|
||||||
|
@ -95,7 +95,7 @@ public class ChangeExpirationTest {
|
||||||
.setExpirationDate(subKeyFingerprint, null, new UnprotectedKeysProtector()).done();
|
.setExpirationDate(subKeyFingerprint, null, new UnprotectedKeysProtector()).done();
|
||||||
|
|
||||||
sInfo = PGPainless.inspectKeyRing(secretKeys);
|
sInfo = PGPainless.inspectKeyRing(secretKeys);
|
||||||
assertNull(sInfo.getExpirationDate(subKeyFingerprint));
|
assertNull(sInfo.getSubkeyExpirationDate(subKeyFingerprint));
|
||||||
assertNull(sInfo.getExpirationDate());
|
assertNull(sInfo.getPrimaryKeyExpirationDate());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@ public class OldSignatureSubpacketsArePreservedOnNewSig {
|
||||||
|
|
||||||
OpenPgpV4Fingerprint subkeyFingerprint = new OpenPgpV4Fingerprint(PGPainless.inspectKeyRing(secretKeys).getPublicKeys().get(1));
|
OpenPgpV4Fingerprint subkeyFingerprint = new OpenPgpV4Fingerprint(PGPainless.inspectKeyRing(secretKeys).getPublicKeys().get(1));
|
||||||
|
|
||||||
PGPSignature oldSignature = PGPainless.inspectKeyRing(secretKeys).getLatestValidSelfOrBindingSignatureOnKey(subkeyFingerprint);
|
PGPSignature oldSignature = PGPainless.inspectKeyRing(secretKeys).getCurrentSubkeyBindingSignature(subkeyFingerprint.getKeyId());
|
||||||
PGPSignatureSubpacketVector oldPackets = oldSignature.getHashedSubPackets();
|
PGPSignatureSubpacketVector oldPackets = oldSignature.getHashedSubPackets();
|
||||||
|
|
||||||
assertEquals(0, oldPackets.getKeyExpirationTime());
|
assertEquals(0, oldPackets.getKeyExpirationTime());
|
||||||
|
@ -56,7 +56,7 @@ public class OldSignatureSubpacketsArePreservedOnNewSig {
|
||||||
secretKeys = PGPainless.modifyKeyRing(secretKeys)
|
secretKeys = PGPainless.modifyKeyRing(secretKeys)
|
||||||
.setExpirationDate(subkeyFingerprint, new Date(), new UnprotectedKeysProtector())
|
.setExpirationDate(subkeyFingerprint, new Date(), new UnprotectedKeysProtector())
|
||||||
.done();
|
.done();
|
||||||
PGPSignature newSignature = PGPainless.inspectKeyRing(secretKeys).getLatestValidSelfOrBindingSignatureOnKey(subkeyFingerprint);
|
PGPSignature newSignature = PGPainless.inspectKeyRing(secretKeys).getCurrentSubkeyBindingSignature(subkeyFingerprint.getKeyId());
|
||||||
PGPSignatureSubpacketVector newPackets = newSignature.getHashedSubPackets();
|
PGPSignatureSubpacketVector newPackets = newSignature.getHashedSubPackets();
|
||||||
|
|
||||||
assertNotEquals(0, newPackets.getKeyExpirationTime());
|
assertNotEquals(0, newPackets.getKeyExpirationTime());
|
||||||
|
|
|
@ -131,9 +131,9 @@ public class RevokeKeyWithoutPreferredAlgorithmsOnPrimaryKey {
|
||||||
secretKeys = modify.done();
|
secretKeys = modify.done();
|
||||||
|
|
||||||
KeyRingInfo info = PGPainless.inspectKeyRing(secretKeys);
|
KeyRingInfo info = PGPainless.inspectKeyRing(secretKeys);
|
||||||
assertEquals(expirationDate.getTime(), info.getExpirationDate().getTime(), 1000);
|
assertEquals(expirationDate.getTime(), info.getPrimaryKeyExpirationDate().getTime(), 1000);
|
||||||
for (OpenPgpV4Fingerprint fingerprint : fingerprintList) {
|
for (OpenPgpV4Fingerprint fingerprint : fingerprintList) {
|
||||||
assertEquals(expirationDate.getTime(), info.getExpirationDate(fingerprint).getTime(), 1000);
|
assertEquals(expirationDate.getTime(), info.getSubkeyExpirationDate(fingerprint).getTime(), 1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,269 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 Paul Schaub.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.pgpainless.signature;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||||
|
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||||
|
import org.bouncycastle.openpgp.PGPSignature;
|
||||||
|
import org.bouncycastle.util.io.Streams;
|
||||||
|
import org.junit.jupiter.api.Disabled;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.pgpainless.PGPainless;
|
||||||
|
import org.pgpainless.decryption_verification.DecryptionStream;
|
||||||
|
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
||||||
|
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||||
|
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||||
|
import org.pgpainless.key.util.KeyRingUtils;
|
||||||
|
import org.pgpainless.util.BCUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test if marker packets are being ignored properly.
|
||||||
|
*
|
||||||
|
* @see <a href="https://tests.sequoia-pgp.org/#Marker_Packet">Sequoia Test-Suite</a>
|
||||||
|
*/
|
||||||
|
public class IgnoreMarkerPackets {
|
||||||
|
|
||||||
|
private static final String KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
|
||||||
|
"Comment: Bob's OpenPGP Transferable Secret Key\n" +
|
||||||
|
"\n" +
|
||||||
|
"lQVYBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" +
|
||||||
|
"/seOXpgecTdOcVttfzC8ycIKrt3aQTiwOG/ctaR4Bk/t6ayNFfdUNxHWk4WCKzdz\n" +
|
||||||
|
"/56fW2O0F23qIRd8UUJp5IIlN4RDdRCtdhVQIAuzvp2oVy/LaS2kxQoKvph/5pQ/\n" +
|
||||||
|
"5whqsyroEWDJoSV0yOb25B/iwk/pLUFoyhDG9bj0kIzDxrEqW+7Ba8nocQlecMF3\n" +
|
||||||
|
"X5KMN5kp2zraLv9dlBBpWW43XktjcCZgMy20SouraVma8Je/ECwUWYUiAZxLIlMv\n" +
|
||||||
|
"9CurEOtxUw6N3RdOtLmYZS9uEnn5y1UkF88o8Nku890uk6BrewFzJyLAx5wRZ4F0\n" +
|
||||||
|
"qV/yq36UWQ0JB/AUGhHVPdFf6pl6eaxBwT5GXvbBUibtf8YI2og5RsgTWtXfU7eb\n" +
|
||||||
|
"SGXrl5ZMpbA6mbfhd0R8aPxWfmDWiIOhBufhMCvUHh1sApMKVZnvIff9/0Dca3wb\n" +
|
||||||
|
"vLIwa3T4CyshfT0AEQEAAQAL/RZqbJW2IqQDCnJi4Ozm++gPqBPiX1RhTWSjwxfM\n" +
|
||||||
|
"cJKUZfzLj414rMKm6Jh1cwwGY9jekROhB9WmwaaKT8HtcIgrZNAlYzANGRCM4TLK\n" +
|
||||||
|
"3VskxfSwKKna8l+s+mZglqbAjUg3wmFuf9Tj2xcUZYmyRm1DEmcN2ZzpvRtHgX7z\n" +
|
||||||
|
"Wn1mAKUlSDJZSQks0zjuMNbupcpyJokdlkUg2+wBznBOTKzgMxVNC9b2g5/tMPUs\n" +
|
||||||
|
"hGGWmF1UH+7AHMTaS6dlmr2ZBIyogdnfUqdNg5sZwsxSNrbglKP4sqe7X61uEAIQ\n" +
|
||||||
|
"bD7rT3LonLbhkrj3I8wilUD8usIwt5IecoHhd9HziqZjRCc1BUBkboUEoyedbDV4\n" +
|
||||||
|
"i4qfsFZ6CEWoLuD5pW7dEp0M+WeuHXO164Rc+LnH6i1VQrpb1Okl4qO6ejIpIjBI\n" +
|
||||||
|
"1t3GshtUu/mwGBBxs60KBX5g77mFQ9lLCRj8lSYqOsHRKBhUp4qM869VA+fD0BRP\n" +
|
||||||
|
"fqPT0I9IH4Oa/A3jYJcg622GwQYA1LhnP208Waf6PkQSJ6kyr8ymY1yVh9VBE/g6\n" +
|
||||||
|
"fRDYA+pkqKnw9wfH2Qho3ysAA+OmVOX8Hldg+Pc0Zs0e5pCavb0En8iFLvTA0Q2E\n" +
|
||||||
|
"LR5rLue9uD7aFuKFU/VdcddY9Ww/vo4k5p/tVGp7F8RYCFn9rSjIWbfvvZi1q5Tx\n" +
|
||||||
|
"+akoZbga+4qQ4WYzB/obdX6SCmi6BndcQ1QdjCCQU6gpYx0MddVERbIp9+2SXDyL\n" +
|
||||||
|
"hpxjSyz+RGsZi/9UAshT4txP4+MZBgDfK3ZqtW+h2/eMRxkANqOJpxSjMyLO/FXN\n" +
|
||||||
|
"WxzTDYeWtHNYiAlOwlQZEPOydZFty9IVzzNFQCIUCGjQ/nNyhw7adSgUk3+BXEx/\n" +
|
||||||
|
"MyJPYY0BYuhLxLYcrfQ9nrhaVKxRJj25SVHj2ASsiwGJRZW4CC3uw40OYxfKEvNC\n" +
|
||||||
|
"mer/VxM3kg8qqGf9KUzJ1dVdAvjyx2Hz6jY2qWCyRQ6IMjWHyd43C4r3jxooYKUC\n" +
|
||||||
|
"YnstRQyb/gCSKahveSEjo07CiXMr88UGALwzEr3npFAsPW3osGaFLj49y1oRe11E\n" +
|
||||||
|
"he9gCHFm+fuzbXrWmdPjYU5/ZdqdojzDqfu4ThfnipknpVUM1o6MQqkjM896FHm8\n" +
|
||||||
|
"zbKVFSMhEP6DPHSCexMFrrSgN03PdwHTO6iBaIBBFqmGY01tmJ03SxvSpiBPON9P\n" +
|
||||||
|
"NVvy/6UZFedTq8A07OUAxO62YUSNtT5pmK2vzs3SAZJmbFbMh+NN204TRI72GlqT\n" +
|
||||||
|
"t5hcfkuv8hrmwPS/ZR6q312mKQ6w/1pqO9qitCFCb2IgQmFiYmFnZSA8Ym9iQG9w\n" +
|
||||||
|
"ZW5wZ3AuZXhhbXBsZT6JAc4EEwEKADgCGwMFCwkIBwIGFQoJCAsCBBYCAwECHgEC\n" +
|
||||||
|
"F4AWIQTRpm4aI7GCyZgPeIz7/MgqAV5zMAUCXaWe+gAKCRD7/MgqAV5zMG9sC/9U\n" +
|
||||||
|
"2T3RrqEbw533FPNfEflhEVRIZ8gDXKM8hU6cqqEzCmzZT6xYTe6sv4y+PJBGXJFX\n" +
|
||||||
|
"yhj0g6FDkSyboM5litOcTupURObVqMgA/Y4UKERznm4fzzH9qek85c4ljtLyNufe\n" +
|
||||||
|
"doL2pp3vkGtn7eD0QFRaLLmnxPKQ/TlZKdLE1G3u8Uot8QHicaR6GnAdc5UXQJE3\n" +
|
||||||
|
"BiV7jZuDyWmZ1cUNwJkKL6oRtp+ZNDOQCrLNLecKHcgCqrpjSQG5oouba1I1Q6Vl\n" +
|
||||||
|
"sP44dhA1nkmLHtxlTOzpeHj4jnk1FaXmyasurrrI5CgU/L2Oi39DGKTH/A/cywDN\n" +
|
||||||
|
"4ZplIQ9zR8enkbXquUZvFDe+Xz+6xRXtb5MwQyWODB3nHw85HocLwRoIN9WdQEI+\n" +
|
||||||
|
"L8a/56AuOwhs8llkSuiITjR7r9SgKJC2WlAHl7E8lhJ3VDW3ELC56KH308d6mwOG\n" +
|
||||||
|
"ZRAqIAKzM1T5FGjMBhq7ZV0eqdEntBh3EcOIfj2M8rg1MzJv+0mHZOIjByawikad\n" +
|
||||||
|
"BVgEXaWc8gEMANYwv1xsYyunXYK0X1vY/rP1NNPvhLyLIE7NpK90YNBj+xS1ldGD\n" +
|
||||||
|
"bUdZqZeef2xJe8gMQg05DoD1DF3GipZ0Ies65beh+d5hegb7N4pzh0LzrBrVNHar\n" +
|
||||||
|
"29b5ExdI7i4iYD5TO6Vr/qTUOiAN/byqELEzAb+L+b2DVz/RoCm4PIp1DU9ewcc2\n" +
|
||||||
|
"WB38Ofqut3nLYA5tqJ9XvAiEQme+qAVcM3ZFcaMt4I4dXhDZZNg+D9LiTWcxdUPB\n" +
|
||||||
|
"leu8iwDRjAgyAhPzpFp+nWoqWA81uIiULWD1Fj+IVoY3ZvgivoYOiEFBJ9lbb4te\n" +
|
||||||
|
"g9m5UT/AaVDTWuHzbspVlbiVe+qyB77C2daWzNyx6UYBPLOo4r0t0c91kbNE5lgj\n" +
|
||||||
|
"Z7xz6los0N1U8vq91EFSeQJoSQ62XWavYmlCLmdNT6BNfgh4icLsT7Vr1QMX9jzn\n" +
|
||||||
|
"JtTPxdXytSdHvpSpULsqJ016l0dtmONcK3z9mj5N5z0k1tg1AH970TGYOe2aUcSx\n" +
|
||||||
|
"IRDMXDOPyzEfjwARAQABAAv9F2CwsjS+Sjh1M1vegJbZjei4gF1HHpEM0K0PSXsp\n" +
|
||||||
|
"SfVvpR4AoSJ4He6CXSMWg0ot8XKtDuZoV9jnJaES5UL9pMAD7JwIOqZm/DYVJM5h\n" +
|
||||||
|
"OASCh1c356/wSbFbzRHPtUdZO9Q30WFNJM5pHbCJPjtNoRmRGkf71RxtvHBzy7np\n" +
|
||||||
|
"Ga+W6U/NVKHw0i0CYwMI0YlKDakYW3Pm+QL+gHZFvngGweTod0f9l2VLLAmeQR/c\n" +
|
||||||
|
"+EZs7lNumhuZ8mXcwhUc9JQIhOkpO+wreDysEFkAcsKbkQP3UDUsA1gFx9pbMzT0\n" +
|
||||||
|
"tr1oZq2a4QBtxShHzP/ph7KLpN+6qtjks3xB/yjTgaGmtrwM8tSe0wD1RwXS+/1o\n" +
|
||||||
|
"BHpXTnQ7TfeOGUAu4KCoOQLv6ELpKWbRBLWuiPwMdbGpvVFALO8+kvKAg9/r+/ny\n" +
|
||||||
|
"zM2GQHY+J3Jh5JxPiJnHfXNZjIKLbFbIPdSKNyJBuazXW8xIa//mEHMI5OcvsZBK\n" +
|
||||||
|
"clAIp7LXzjEjKXIwHwDcTn9pBgDpdOKTHOtJ3JUKx0rWVsDH6wq6iKV/FTVSY5jl\n" +
|
||||||
|
"zN+puOEsskF1Lfxn9JsJihAVO3yNsp6RvkKtyNlFazaCVKtDAmkjoh60XNxcNRqr\n" +
|
||||||
|
"gCnwdpbgdHP6v/hvZY54ZaJjz6L2e8unNEkYLxDt8cmAyGPgH2XgL7giHIp9jrsQ\n" +
|
||||||
|
"aS381gnYwNX6wE1aEikgtY91nqJjwPlibF9avSyYQoMtEqM/1UjTjB2KdD/MitK5\n" +
|
||||||
|
"fP0VpvuXpNYZedmyq4UOMwdkiNMGAOrfmOeT0olgLrTMT5H97Cn3Yxbk13uXHNu/\n" +
|
||||||
|
"ZUZZNe8s+QtuLfUlKAJtLEUutN33TlWQY522FV0m17S+b80xJib3yZVJteVurrh5\n" +
|
||||||
|
"HSWHAM+zghQAvCesg5CLXa2dNMkTCmZKgCBvfDLZuZbjFwnwCI6u/NhOY9egKuUf\n" +
|
||||||
|
"SA/je/RXaT8m5VxLYMxwqQXKApzD87fv0tLPlVIEvjEsaf992tFEFSNPcG1l/jpd\n" +
|
||||||
|
"5AVXw6kKuf85UkJtYR1x2MkQDrqY1QX/XMw00kt8y9kMZUre19aCArcmor+hDhRJ\n" +
|
||||||
|
"E3Gt4QJrD9z/bICESw4b4z2DbgD/Xz9IXsA/r9cKiM1h5QMtXvuhyfVeM01enhxM\n" +
|
||||||
|
"GbOH3gjqqGNKysx0UODGEwr6AV9hAd8RWXMchJLaExK9J5SRawSg671ObAU24SdY\n" +
|
||||||
|
"vMQ9Z4kAQ2+1ReUZzf3ogSMRZtMT+d18gT6L90/y+APZIaoArLPhebIAGq39HLmJ\n" +
|
||||||
|
"26x3z0WAgrpA1kNsjXEXkoiZGPLKIGoe3hqJAbYEGAEKACAWIQTRpm4aI7GCyZgP\n" +
|
||||||
|
"eIz7/MgqAV5zMAUCXaWc8gIbDAAKCRD7/MgqAV5zMOn/C/9ugt+HZIwX308zI+QX\n" +
|
||||||
|
"c5vDLReuzmJ3ieE0DMO/uNSC+K1XEioSIZP91HeZJ2kbT9nn9fuReuoff0T0Dief\n" +
|
||||||
|
"rbwcIQQHFFkrqSp1K3VWmUGp2JrUsXFVdjy/fkBIjTd7c5boWljv/6wAsSfiv2V0\n" +
|
||||||
|
"JSM8EFU6TYXxswGjFVfc6X97tJNeIrXL+mpSmPPqy2bztcCCHkWS5lNLWQw+R7Vg\n" +
|
||||||
|
"71Fe6yBSNVrqC2/imYG2J9zlowjx1XU63Wdgqp2Wxt0l8OmsB/W80S1fRF5G4SDH\n" +
|
||||||
|
"s9HXglXXqPsBRZJYfP+VStm9L5P/sKjCcX6WtZR7yS6G8zj/X767MLK/djANvpPd\n" +
|
||||||
|
"NVniEke6hM3CNBXYPAMhQBMWhCulcoz+0lxi8L34rMN+Dsbma96psdUrn7uLaB91\n" +
|
||||||
|
"6we0CTfF8qqm7BsVAgalon/UUiuMY80U3ueoj3okiSTiHIjD/YtpXSPioC8nMng7\n" +
|
||||||
|
"xqAY9Bwizt4FWgXuLm1a4+So4V9j1TRCXd12Uc2l2RNmgDE=\n" +
|
||||||
|
"=miES\n" +
|
||||||
|
"-----END PGP PRIVATE KEY BLOCK-----\n";
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void markerPlusDetachedSignature() throws IOException, PGPException {
|
||||||
|
String sig = "-----BEGIN PGP SIGNATURE-----\n" +
|
||||||
|
"\n" +
|
||||||
|
"ygNQR1DCwTsEAAEKAG8FgmB9Y8YJEPv8yCoBXnMwRxQAAAAAAB4AIHNhbHRAbm90\n" +
|
||||||
|
"YXRpb25zLnNlcXVvaWEtcGdwLm9yZ1j1pQ8+YA70OJUxn1bZxiCar4WPrLMuM2By\n" +
|
||||||
|
"IITRjS1OFiEE0aZuGiOxgsmYD3iM+/zIKgFeczAAAOdzDACDhEptUvTFB7gx4YYG\n" +
|
||||||
|
"fFCaPxpFNo8zKnlcB2g1cFkrKEpZ/2It3ozf0beL81TUaj7G0Z4iJVDR4ei6Zrdt\n" +
|
||||||
|
"93GZRx+zQ6h3Wpj3TAi9mTHx5VRrMKK32o6VwRPuZy/KYCrst/eaM9LdhvAGsevR\n" +
|
||||||
|
"aQfopMB1xS+/8ySGimOfD6NwUWuLiOUr9fvAf3UyhpZiHL4UJ2mB1rTbQJtM++yf\n" +
|
||||||
|
"U48k+YsOVas/7B9qxlw3XsYvjVaFcTrKOj0lBn2uy2NMJje9dG+ll1lfdDkaqFFM\n" +
|
||||||
|
"FNgiJqGeoQ0whIsURurhzcY5zgujEw0qXRLMblI+g+yw2THrNx07EArnr2WzVzIP\n" +
|
||||||
|
"ifMu939eqm+mP0NKA1jVAPIIm92ZtIKD3+YzyczIepvLx4FwU1y5eAMotc76JrAg\n" +
|
||||||
|
"VWR7+FdtSA63VnVvLBR6YX7C0PVGR6BJBLEOFcZjNoW/JhN6gpmUvJLeZkFogC+J\n" +
|
||||||
|
"+J5EAJeGsE8/f/gi6pLtgAhjCNzH0qltOZsdJAfXqmd0NJ4=\n" +
|
||||||
|
"=5tQ4\n" +
|
||||||
|
"-----END PGP SIGNATURE-----\n";
|
||||||
|
|
||||||
|
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(KEY);
|
||||||
|
PGPPublicKeyRing publicKeys = KeyRingUtils.publicKeyRingFrom(secretKeys);
|
||||||
|
String data = "Marker + Detached signature";
|
||||||
|
PGPSignature signature = BCUtil.readSignatures(sig).get(0);
|
||||||
|
|
||||||
|
DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify().onInputStream(new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8)))
|
||||||
|
.doNotDecrypt()
|
||||||
|
.verifyDetachedSignature(signature)
|
||||||
|
.verifyWith(publicKeys)
|
||||||
|
.ignoreMissingPublicKeys()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
Streams.pipeAll(decryptionStream, outputStream);
|
||||||
|
|
||||||
|
decryptionStream.close();
|
||||||
|
OpenPgpMetadata metadata = decryptionStream.getResult();
|
||||||
|
assertTrue(metadata.getVerifiedSignatures().containsKey(new OpenPgpV4Fingerprint("D1A66E1A23B182C9980F788CFBFCC82A015E7330")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void markerPlusEncryptedMessage() throws IOException, PGPException {
|
||||||
|
String msg = "-----BEGIN PGP MESSAGE-----\n" +
|
||||||
|
"\n" +
|
||||||
|
"ygNQR1DBwMwDfC+qTfk8N7IBC/4sXk4Al5Hdl38XCky41A1+HdJcDbXJ70/OY34M\n" +
|
||||||
|
"QYvco/Yk5Lb5XXg2adtSlwM2C3d6a8JMwmU7qFP/pEDXijJiTD+NabbsWO2+BwGd\n" +
|
||||||
|
"H9aTzAQhUj8PK4io5Q4SyxELosp9uO5XCkMkps5ev91mACwxm2p79tp7qkj8h4Q/\n" +
|
||||||
|
"3j8Hc9Ea4o0WCKuTIO1p42nX7gHMaUPkmZqnUxhN7ZkUgC4AHsjDgteK0viUdkmg\n" +
|
||||||
|
"aLza3TwN/e+vgnO8ypH1wBfdkvW+Ose5WPv587XGHZLsTQI6v2WeJo3K6KeuYXGI\n" +
|
||||||
|
"lxx0kLnaUL+9TswQAfU1jeh3OEy/eUIsMXc+4miYmE0cg4QxfP3ERyeziJpkzYgJ\n" +
|
||||||
|
"hGwK6cNfQiUXpWpyaAZ1vkCPEc074HIriGssq/CbkqUyUtbMzRuqfujZfMHiFkRc\n" +
|
||||||
|
"VvHkR3gzvYlMFhSjHVM2Dx9wJWmQqStdeHKfnZg4fZXDD7/zy+xRPVQgQBWelSTR\n" +
|
||||||
|
"4etbOP4OKLDX4LttGEPwy45KJvLSwZgBbU64o/RGNEbUt6dAXy3QeU5AkVR3h94f\n" +
|
||||||
|
"sda0b04cn2ZAIAuvWdF5MsgxxACFYOIHwobHzZGW54TTpTMzLVKJo8XuOuGUQE58\n" +
|
||||||
|
"jKJ+EiXZ3+TrItbhinUkZBdS59cleq2kNZ2dmTZ/ZkBwcOK1hH//Q8qrdLugm+h0\n" +
|
||||||
|
"YD/OOKudZTEu93TISGb6VeeWsb8UVjncbb2S/A/9/huFgUNYu7zma4231Y6OFx2J\n" +
|
||||||
|
"0tMVA+trKRWNhoacGwTl4mZnel42+IlgY6qVU+oTOCOtWNzuAeyj7PjnSMizv14u\n" +
|
||||||
|
"PvVaWDJv61yxNrsnLRjUX0D+CcrYMn10672ICDKg+L9SiXe87saBmsegDYSQWwE9\n" +
|
||||||
|
"WOtCWaEnKOSjD/Fewy/547dQJQihb9lG37wdM24t+J4qdkPkYKsUMhE3NcdrtLqJ\n" +
|
||||||
|
"QO7qDhEpxoX8lloNcEAFo0p+HqgcKX86/AzDYHxoLHKqDOYUQEHcKCfwA7mSXBDV\n" +
|
||||||
|
"JaOSO5Z2Jz+4HwvnD2ZgHP+qgctx87M5AgUQzlHm5mmBj3U3dvQQr6vIB4xtWKuy\n" +
|
||||||
|
"DganN7X2Jb5wlODBntdlyoM7FUHE1GYsHI2HkGl6d3bGAcEQdy9NjQyiVuWBqbr8\n" +
|
||||||
|
"OFDpFglcc70anHB091USc25LO23IhhXQ9ORalzULfoixf5lo6lmW3MPJGuYAhoIp\n" +
|
||||||
|
"SA8m91mkrqvtBckHA7xE2LdDM5JbAFNPYZzvzz5pAgYTEQp1mJB25Va1G2QHZhos\n" +
|
||||||
|
"dwvafpzRdOUaHM7lpvzTn3o3rM/Ntqfb6wn7GylsFYNpq+Rgtt6Mea68yTh+AeQ=\n" +
|
||||||
|
"=VSZ3\n" +
|
||||||
|
"-----END PGP MESSAGE-----\n";
|
||||||
|
|
||||||
|
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(KEY);
|
||||||
|
PGPPublicKeyRing publicKeys = KeyRingUtils.publicKeyRingFrom(secretKeys);
|
||||||
|
String data = "Marker + Encrypted Message";
|
||||||
|
|
||||||
|
DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify().onInputStream(new ByteArrayInputStream(msg.getBytes(StandardCharsets.UTF_8)))
|
||||||
|
.decryptWith(SecretKeyRingProtector.unprotectedKeys(), secretKeys)
|
||||||
|
.verifyWith(publicKeys)
|
||||||
|
.ignoreMissingPublicKeys()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
Streams.pipeAll(decryptionStream, outputStream);
|
||||||
|
|
||||||
|
decryptionStream.close();
|
||||||
|
assertArrayEquals(data.getBytes(StandardCharsets.UTF_8), outputStream.toByteArray());
|
||||||
|
OpenPgpMetadata metadata = decryptionStream.getResult();
|
||||||
|
assertTrue(metadata.getVerifiedSignatures().containsKey(new OpenPgpV4Fingerprint("D1A66E1A23B182C9980F788CFBFCC82A015E7330")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Disabled // TODO: Fix upstreamed. Enable once BC is bumped
|
||||||
|
public void markerPlusCertificate() throws IOException {
|
||||||
|
String pubKeyBlock = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
|
||||||
|
"\n" +
|
||||||
|
"ygNQR1DGwM0EXaWc8gEMALlwv09ChAtMy6GNti9vg1y66jRt+aA9EXLBfneM+GgV\n" +
|
||||||
|
"/hmDqa/+x45emB5xN05xW21/MLzJwgqu3dpBOLA4b9y1pHgGT+3prI0V91Q3EdaT\n" +
|
||||||
|
"hYIrN3P/np9bY7QXbeohF3xRQmnkgiU3hEN1EK12FVAgC7O+nahXL8tpLaTFCgq+\n" +
|
||||||
|
"mH/mlD/nCGqzKugRYMmhJXTI5vbkH+LCT+ktQWjKEMb1uPSQjMPGsSpb7sFryehx\n" +
|
||||||
|
"CV5wwXdfkow3mSnbOtou/12UEGlZbjdeS2NwJmAzLbRKi6tpWZrwl78QLBRZhSIB\n" +
|
||||||
|
"nEsiUy/0K6sQ63FTDo3dF060uZhlL24SefnLVSQXzyjw2S7z3S6ToGt7AXMnIsDH\n" +
|
||||||
|
"nBFngXSpX/KrfpRZDQkH8BQaEdU90V/qmXp5rEHBPkZe9sFSJu1/xgjaiDlGyBNa\n" +
|
||||||
|
"1d9Tt5tIZeuXlkylsDqZt+F3RHxo/FZ+YNaIg6EG5+EwK9QeHWwCkwpVme8h9/3/\n" +
|
||||||
|
"QNxrfBu8sjBrdPgLKyF9PQARAQABzSFCb2IgQmFiYmFnZSA8Ym9iQG9wZW5wZ3Au\n" +
|
||||||
|
"ZXhhbXBsZT7CwQ4EEwEKADgCGwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AWIQTR\n" +
|
||||||
|
"pm4aI7GCyZgPeIz7/MgqAV5zMAUCXaWe+gAKCRD7/MgqAV5zMG9sC/9U2T3RrqEb\n" +
|
||||||
|
"w533FPNfEflhEVRIZ8gDXKM8hU6cqqEzCmzZT6xYTe6sv4y+PJBGXJFXyhj0g6FD\n" +
|
||||||
|
"kSyboM5litOcTupURObVqMgA/Y4UKERznm4fzzH9qek85c4ljtLyNufedoL2pp3v\n" +
|
||||||
|
"kGtn7eD0QFRaLLmnxPKQ/TlZKdLE1G3u8Uot8QHicaR6GnAdc5UXQJE3BiV7jZuD\n" +
|
||||||
|
"yWmZ1cUNwJkKL6oRtp+ZNDOQCrLNLecKHcgCqrpjSQG5oouba1I1Q6VlsP44dhA1\n" +
|
||||||
|
"nkmLHtxlTOzpeHj4jnk1FaXmyasurrrI5CgU/L2Oi39DGKTH/A/cywDN4ZplIQ9z\n" +
|
||||||
|
"R8enkbXquUZvFDe+Xz+6xRXtb5MwQyWODB3nHw85HocLwRoIN9WdQEI+L8a/56Au\n" +
|
||||||
|
"Owhs8llkSuiITjR7r9SgKJC2WlAHl7E8lhJ3VDW3ELC56KH308d6mwOGZRAqIAKz\n" +
|
||||||
|
"M1T5FGjMBhq7ZV0eqdEntBh3EcOIfj2M8rg1MzJv+0mHZOIjByawikbOwM0EXaWc\n" +
|
||||||
|
"8gEMANYwv1xsYyunXYK0X1vY/rP1NNPvhLyLIE7NpK90YNBj+xS1ldGDbUdZqZee\n" +
|
||||||
|
"f2xJe8gMQg05DoD1DF3GipZ0Ies65beh+d5hegb7N4pzh0LzrBrVNHar29b5ExdI\n" +
|
||||||
|
"7i4iYD5TO6Vr/qTUOiAN/byqELEzAb+L+b2DVz/RoCm4PIp1DU9ewcc2WB38Ofqu\n" +
|
||||||
|
"t3nLYA5tqJ9XvAiEQme+qAVcM3ZFcaMt4I4dXhDZZNg+D9LiTWcxdUPBleu8iwDR\n" +
|
||||||
|
"jAgyAhPzpFp+nWoqWA81uIiULWD1Fj+IVoY3ZvgivoYOiEFBJ9lbb4teg9m5UT/A\n" +
|
||||||
|
"aVDTWuHzbspVlbiVe+qyB77C2daWzNyx6UYBPLOo4r0t0c91kbNE5lgjZ7xz6los\n" +
|
||||||
|
"0N1U8vq91EFSeQJoSQ62XWavYmlCLmdNT6BNfgh4icLsT7Vr1QMX9jznJtTPxdXy\n" +
|
||||||
|
"tSdHvpSpULsqJ016l0dtmONcK3z9mj5N5z0k1tg1AH970TGYOe2aUcSxIRDMXDOP\n" +
|
||||||
|
"yzEfjwARAQABwsD2BBgBCgAgFiEE0aZuGiOxgsmYD3iM+/zIKgFeczAFAl2lnPIC\n" +
|
||||||
|
"GwwACgkQ+/zIKgFeczDp/wv/boLfh2SMF99PMyPkF3Obwy0Xrs5id4nhNAzDv7jU\n" +
|
||||||
|
"gvitVxIqEiGT/dR3mSdpG0/Z5/X7kXrqH39E9A4nn628HCEEBxRZK6kqdSt1VplB\n" +
|
||||||
|
"qdia1LFxVXY8v35ASI03e3OW6FpY7/+sALEn4r9ldCUjPBBVOk2F8bMBoxVX3Ol/\n" +
|
||||||
|
"e7STXiK1y/pqUpjz6stm87XAgh5FkuZTS1kMPke1YO9RXusgUjVa6gtv4pmBtifc\n" +
|
||||||
|
"5aMI8dV1Ot1nYKqdlsbdJfDprAf1vNEtX0ReRuEgx7PR14JV16j7AUWSWHz/lUrZ\n" +
|
||||||
|
"vS+T/7CownF+lrWUe8kuhvM4/1++uzCyv3YwDb6T3TVZ4hJHuoTNwjQV2DwDIUAT\n" +
|
||||||
|
"FoQrpXKM/tJcYvC9+KzDfg7G5mveqbHVK5+7i2gfdesHtAk3xfKqpuwbFQIGpaJ/\n" +
|
||||||
|
"1FIrjGPNFN7nqI96JIkk4hyIw/2LaV0j4qAvJzJ4O8agGPQcIs7eBVoF7i5tWuPk\n" +
|
||||||
|
"qOFfY9U0Ql3ddlHNpdkTZoAx\n" +
|
||||||
|
"=TrY7\n" +
|
||||||
|
"-----END PGP PUBLIC KEY BLOCK-----\n";
|
||||||
|
|
||||||
|
PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(pubKeyBlock);
|
||||||
|
assertEquals(new OpenPgpV4Fingerprint("D1A66E1A23B182C9980F788CFBFCC82A015E7330"), new OpenPgpV4Fingerprint(publicKeys));
|
||||||
|
assertNotNull(publicKeys.getPublicKey(new OpenPgpV4Fingerprint("1DDCE15F09217CEE2F3B37607C2FAA4DF93C37B2").getKeyId()));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,265 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 Paul Schaub.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.pgpainless.signature;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||||
|
import org.bouncycastle.openpgp.PGPSignature;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.pgpainless.PGPainless;
|
||||||
|
import org.pgpainless.util.BCUtil;
|
||||||
|
|
||||||
|
public class KeyRevocationTest {
|
||||||
|
|
||||||
|
private static final String data = "Hello, World";
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void subkeySignsPrimaryKeyRevokedNoReason() throws IOException, SignatureValidationException {
|
||||||
|
String key = "-----BEGIN PGP ARMORED FILE-----\n" +
|
||||||
|
"Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" +
|
||||||
|
"\n" +
|
||||||
|
"xsBNBFpJegABCAC1ePFquP0135m8DYhcybhv7l+ecojitFOd/jRM7hCczIqKgalD\n" +
|
||||||
|
"1Ro1gNr3VmH6FjRIKIvGT+sOzCKne1v3KyAAPoxtwxjkATTKdOGo15I6v5ZjmO1d\n" +
|
||||||
|
"rLQOLSt1TF7XbQSt+ns6PUZWJL907DvECUU5b9FkNUqfQ14QqY+gi7MOyAQez3b7\n" +
|
||||||
|
"Pg5Cyz/kVWQ6TSMW/myDEDEertQ4rDBsptEDFHCC2+iF4hO2LqfiCriu5qyLcKCQ\n" +
|
||||||
|
"pd6dEuwJQ/jjT0D9A9Fwf+i04x6ZPKSU9oNAWqn8OSAq3/0B/hu9V+0U0iHPnJxe\n" +
|
||||||
|
"quykvJk7maxhiGhxBWYXTvDJmoon0NOles7LABEBAAHCwHMEIAEKAAYFglwqrYAA\n" +
|
||||||
|
"IQkQaE+tYtwDj7sWIQTy0VCk/piSXVHpFTloT61i3AOPu9wIB/sEXov0SN63jhHX\n" +
|
||||||
|
"aVQWVWukecit/9BYQHpxRcHC6cBdxfw8peGXXyRnr0hJn9USKDH87KvmLHbjGUMd\n" +
|
||||||
|
"aILnSc6klWtuB5HTu2S6LppUnQHUciavSZUc1P1A0BbmXoMyI0zNna1UP/n3kPgP\n" +
|
||||||
|
"YT7yTpLROkTY2us10s59cuYWVXzQT4MfEGQVZ/2YBXErGKhafkCDHe1XPhEpJ8/K\n" +
|
||||||
|
"mXhk3gQjflm43E7hhZuo/Qo2lCU8XCOibe08J0zPsOTY3fwFV2Vqyq9HS39YYMhj\n" +
|
||||||
|
"QJfxxICJjAtzE8v+ze3QWzTEWxqLbNKeQ1FXuYW7wMQv8HHoJTgfnIcn1Lsihlvl\n" +
|
||||||
|
"ph4T7B+jwsB8BB8BCgAPBYJeC+EAAhUKApsDAh4BACEJEGhPrWLcA4+7FiEE8tFQ\n" +
|
||||||
|
"pP6Ykl1R6RU5aE+tYtwDj7vH3wf/UOtHYOtKoQNqz53f9XQi9gfnPEVp6uOD6Yox\n" +
|
||||||
|
"N4ANUUL3EUBOYzczEqPzJxtJki+cB3k7I0nfw0SN5xz9Oq7OkLm9dTaCoTbsmt5m\n" +
|
||||||
|
"s/YMs3mTHP4zYm/N/wcxQq8bEkJvvVh7q8V3llzjzC2bN8Uv4xtBA7QidhZuFBdf\n" +
|
||||||
|
"X3CMncDf7LBeDRqXwmPNvsE0seI6CN3ESjmwhSWmgYBuZ5fnha+3H4xCLqgiQmkL\n" +
|
||||||
|
"F4qgXu3eldyqjdfLfgoEmsmzGV3MrEP1EsRJC4SAdqTmcHM+BN00xYMUQMXK+HLO\n" +
|
||||||
|
"AXdj0c92eRSk86NJmvxbdFHxSUfnwnLOefp+pAvStMvxOwWNocLAfAQfAQoADwWC\n" +
|
||||||
|
"Wkl6AAIVCgKbAwIeAQAhCRBoT61i3AOPuxYhBPLRUKT+mJJdUekVOWhPrWLcA4+7\n" +
|
||||||
|
"qg0H+wakw+hjU8Fzzrf9JB0D2Jm3SLV/qVj/qN42gelUxw13J9u27bQOTPNhF2X2\n" +
|
||||||
|
"nuPmwtXTAftbi2peHIlYDvSJiQvTcLOX3NyR+Eebrkr6Y847nZCbBrt3AChN4cIk\n" +
|
||||||
|
"/dzIurehDaSwg0sascwJn6DkG1SWFmO4D+2eAo9CAD9vWqaxHNCVqDIxyqSoGBer\n" +
|
||||||
|
"tLFoB1gbhF5P+qOhmG9h2WCuMnKWNllyoFYcu/4kA2DtRTn3FkFx1Ri5/DsyC46G\n" +
|
||||||
|
"yqITXp009tGYQAEoty1A0gzE0H8UklbdJ4c1rlySeEfD81FXkSdANKDMo2VR4rxw\n" +
|
||||||
|
"uhsDLIkLklE8fHvxpzgcjqnxp3vNEmp1bGlldEBleGFtcGxlLm9yZ8LAcwQTAQoA\n" +
|
||||||
|
"BgWCWkl6AAAhCRBoT61i3AOPuxYhBPLRUKT+mJJdUekVOWhPrWLcA4+7Q30H/j58\n" +
|
||||||
|
"bCBbv9I7igVI47QGH0c5K62LTHqT47Wk7xn6NUs1JF/+hfxo3UnlYOSKumHAa4/H\n" +
|
||||||
|
"PnAdxKGaR50nE4FkB8HHlkC3fR4W4E61Db1tXoNglczdEQbmDLVrvSTOKR+7LCVt\n" +
|
||||||
|
"TZjfvzfQeu6m9sviXwcB/5WudPDcwq5d8Vk8Y0+cDcvhRpDWYNt7P9KpTtrsQo3P\n" +
|
||||||
|
"pkQLgJaeRJkzlVjiAWzNMbbYwTsd/WZllkCiWdJ6xYytY6aR6ms8FljV+0R5CvNb\n" +
|
||||||
|
"ZW0lLTj6EyCQ89/DWosjHGR96faJDdw1OF7RfqBNfDPmfxNMVEJrpm5Goxc3FI5n\n" +
|
||||||
|
"e3p6Mz+QZxhEs3TUuV3OwE0EWkrLgAEIAMpG/LapVl7ahxhpAagKtxJ6Zrwaqyr6\n" +
|
||||||
|
"no47WSgF6hLMyfTZYmwhlLi8JzTlTkf4XDlJThh/nMjHAKQViQfu3f/NytXggnb/\n" +
|
||||||
|
"PJwxVWr+4SiypRAW2STR4B6Sd9d6ZXrcwkeMd0kxCEqxLTu9ZdhwXaykmF2sRoCC\n" +
|
||||||
|
"8WMHEot4thI88UQ8vtM8svLO3fjg+UoRWHsYEqyh918dXMUACcxhiVce+Rx1PRlY\n" +
|
||||||
|
"d8R8Ce5w5tuisx3oBtlzyAVyds/L5rElU1so9AI0+JFVWqTdH/OACio8kO34Lgs1\n" +
|
||||||
|
"xhxVfY5sQ8fmB/vR4YyKx0s2w8evuMMkbMY+0rvobv26ICdvJ52080MAEQEAAcLB\n" +
|
||||||
|
"rAQYAQoACQWCXgvhAAKbAgFXCRBoT61i3AOPu8B0oAQZAQoABgWCXgvhAAAhCRBK\n" +
|
||||||
|
"cjSjoSE6ZRYhBFF5LA5I4v2pTpO5EUpyNKOhITplp0kIAIrv83RJh3+lm8H27P3O\n" +
|
||||||
|
"hTm3z8Rrsy5EK+H2SnKivNTLUdZodVlSyUYF1uLvHB7Wch+aU4Z4DHFIss1rGtIO\n" +
|
||||||
|
"iWs/MOrK/1r93tanUwiE7JDK1gg2qA4Q9rXgI5lrpPbvGQTye8YZnvkP1EPdMaJk\n" +
|
||||||
|
"PzXQiWn4q5Ng7Pdqeze0SkhEtSssAYXzjSWz8NU3WfTLbPgxo5LnGG3vmcz8ay6V\n" +
|
||||||
|
"l7q9QUhhKgbUwBlt3Uv8acAWDZYWrFx42DK+B3iGGGDsfqEeSYA2KFX6dpNA8Cv0\n" +
|
||||||
|
"F6IG42vv1Y7/i613TWNLdWwN+RTZ5et+zPIgja17yKERQEWzcoHvHP40lhjywf7S\n" +
|
||||||
|
"MjYWIQTy0VCk/piSXVHpFTloT61i3AOPuxS8CACtRp4DTJ67sVjOBKIISk0pija3\n" +
|
||||||
|
"eqf3d1rHfsttNfQOzc/uDsnZBA75jVVYZVHH4Dn9i+gX+t8HTdIaPjg4QrjUqh3u\n" +
|
||||||
|
"jS9TYXSE2zBpw3Sm+eyCAfQriRaSC5/S2dRIuiTxKZqYkhGi/lSbdXzJ33PI7RfD\n" +
|
||||||
|
"d1nEVXybKtWrJV3vDaYO9PWFYJtjl7DVoJLZfX3IruBDU8m0Bo6TfVk2tWlNZ5JK\n" +
|
||||||
|
"OjVKCH47TPjzuFVO8dNDPnUybGBoZ3PehLU/BH0gCBQSmUQJDARYRHHZMWvIQiiN\n" +
|
||||||
|
"/p8iN4E6tE3BUk98MtOQJqFe8JYM1ADLFuzFdjaRu3ybpdkO6bisPrnQVHNEwsGs\n" +
|
||||||
|
"BBgBCgAJBYJa6P+AApsCAVcJEGhPrWLcA4+7wHSgBBkBCgAGBYJa6P+AACEJEEpy\n" +
|
||||||
|
"NKOhITplFiEEUXksDkji/alOk7kRSnI0o6EhOmXhRwf/do4VE16xIIaOg2IZlRbl\n" +
|
||||||
|
"2tzRoQIyMmaN8mBzKC/Wmdw1Mo8YQMkQ6SNgq2oUOCbD4Xo9pvt3x1mt+P7W+ZqR\n" +
|
||||||
|
"2BVhGoUL3VkhQnFO6djVCnKtszQOosTtvn0EIZm62EfkxcWJoS4whlDbdeBP12iC\n" +
|
||||||
|
"9VcT0DgOSm4kT6WvAbFDZTYpPQEj1sp9GQNK4ydWVe5yWq11W7mQxHFA7g5t3AOb\n" +
|
||||||
|
"bqe47gfH089gQ3INymvjnDxM9BoGX6vSuNHYt6/SBywYTTx4nhVSI/Y/ycjJ071T\n" +
|
||||||
|
"nHjNyf0W9DAliVW1zQSqUTA4mwkIfu326skBDP8yKZpNE4AaU2WajD9IMWHViJk9\n" +
|
||||||
|
"SBYhBPLRUKT+mJJdUekVOWhPrWLcA4+7TrYIAIYAKrzgdeNi9kpEt2SHcLoQLViz\n" +
|
||||||
|
"xwrRMATqhrT/GdtOK6gJm5ycps6O+/jk/kknJw068MzlCZwotKj1MX7sYbx8ZwcQ\n" +
|
||||||
|
"SI2qDHBfvoirKhdb3+lrlzo2ydTfCNPKQdp4obeTMSGfazBg3gEo+/V+yPSY87Hd\n" +
|
||||||
|
"9DlRn02cst1cmD8XCep/7GaHDZmk79PxfCt04q0h+iQ13WOc4q0YvfRid0fgC+js\n" +
|
||||||
|
"8awobryxUhLSESa1uV1X4N8IXNFw/uSfUbB6C997m/WYUBxSrI639JxmGxBcDIUn\n" +
|
||||||
|
"crH02GDG8CotAnEHkLTz9GPO80q8mowzBV0EtHsXb4TeAFw5T5Qd0a5I+wk=\n" +
|
||||||
|
"=2oji\n" +
|
||||||
|
"-----END PGP ARMORED FILE-----\n";
|
||||||
|
String sigT0 = "-----BEGIN PGP ARMORED FILE-----\n" +
|
||||||
|
"Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" +
|
||||||
|
"\n" +
|
||||||
|
"wsBzBAABCgAGBYJYaEaAACEJEGhPrWLcA4+7FiEE8tFQpP6Ykl1R6RU5aE+tYtwD\n" +
|
||||||
|
"j7ttqgf9Gp4T5Q19cNL9Eyz1nlw11HDHT1wxfGHU5li76y7oo4Jqim15sEPDJWmc\n" +
|
||||||
|
"IpYVrczpCI95aCuaE6yfzpjZlXMzftwex3DjM98vyZH4W9teKcOnpAVjn3dLoQJA\n" +
|
||||||
|
"i4fiq3VaLgl+1OYOwu3DmwGKJZubHM3oPia9pbuyvL5Scvx+QCG0AVnssnt2QswG\n" +
|
||||||
|
"uU6J35QgBdjG2iC043sUoyxTSk929iamdQnOGchjcaATb4E4+HvtkRy4IirKxiKK\n" +
|
||||||
|
"c535BHJRLgWQzUcDDZ5kHf3SPLTNsigFFEwUyf5voFcn/DSMWSzPaVecaafTVJW2\n" +
|
||||||
|
"u8G1R5mjuxDRup8p//X1BSk1FpSmvw==\n" +
|
||||||
|
"=3/dv\n" +
|
||||||
|
"-----END PGP ARMORED FILE-----\n";
|
||||||
|
String sigT1T2 = "-----BEGIN PGP ARMORED FILE-----\n" +
|
||||||
|
"Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" +
|
||||||
|
"\n" +
|
||||||
|
"wsBzBAABCgAGBYJa564AACEJEGhPrWLcA4+7FiEE8tFQpP6Ykl1R6RU5aE+tYtwD\n" +
|
||||||
|
"j7ufRgf/QOsaJZgQaQ5daQrfBItOEcW+5acgY1TCwMVmc/nzBqC32TOvMaM3dypf\n" +
|
||||||
|
"wJbqzxHQIes+ivKDF872VWlMA2BErifpdsogbS0pdik/qU+AjMhr188xKpZKG/IY\n" +
|
||||||
|
"6BtuUPeSpsimx3UeEN3kt79fMtewBo0EXo3ujCyPpIF/9Vpd7L9jlJSvRBuM0/aR\n" +
|
||||||
|
"gbRsclEw4JZ98B3t7F3rLmx+F57Zre0ctzT4tHE6IaCYpEClr6Tepj/UxExYOt2l\n" +
|
||||||
|
"hKgVN8Wsuug7XYdOFmxqrV967m3CTnF8AspmxwgKK6NXjVLplfqij7Rp2URPWrWn\n" +
|
||||||
|
"Pp3CQRGUWJdMeMY9P1MpFq6XiXtftw==\n" +
|
||||||
|
"=Ld1q\n" +
|
||||||
|
"-----END PGP ARMORED FILE-----\n";
|
||||||
|
String sigT2T3 = "-----BEGIN PGP ARMORED FILE-----\n" +
|
||||||
|
"Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" +
|
||||||
|
"\n" +
|
||||||
|
"wsBzBAABCgAGBYJdP4iAACEJEGhPrWLcA4+7FiEE8tFQpP6Ykl1R6RU5aE+tYtwD\n" +
|
||||||
|
"j7sYXQf8CZw6Kx4oyI8ZJ2c9RjVZmUFEirAoXH7oYA+Ye+wSAY9OtqE/x2SOYaC6\n" +
|
||||||
|
"QHiB93/wkvpqCVkLy2lenzpD7WXLbuFZ+/5jXp1o+sVHXfLSWo6pfIhOjj9FSr8x\n" +
|
||||||
|
"qqlqUfKwkbA6WYgju+qKC35SYdSptix7unaFkO41UdsM8wGQh880HSRMBMFPzg07\n" +
|
||||||
|
"3hMNYXoEJjFlIkxJSMu2WL7N0Q/4xE2iJftsQjUYAtJ/C/YK2I6dhW+CZremnv5R\n" +
|
||||||
|
"/8W+oH5Q63lYU8YL4wYnJQvkHjKs/kjLpoPmqL8kdHjndSpU+KOYr5w61XuEp2hp\n" +
|
||||||
|
"r8trtljVaVIQX2rYawSlqKkWXt0yag==\n" +
|
||||||
|
"=xVd8\n" +
|
||||||
|
"-----END PGP ARMORED FILE-----\n";
|
||||||
|
String sigT3Now = "-----BEGIN PGP ARMORED FILE-----\n" +
|
||||||
|
"Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" +
|
||||||
|
"\n" +
|
||||||
|
"wsBzBAABCgAGBYJe/cFVACEJEGhPrWLcA4+7FiEE8tFQpP6Ykl1R6RU5aE+tYtwD\n" +
|
||||||
|
"j7vmhQf/UB456IXc8ub8HTExab1d5KqOGSUWpwIznTu8Wk8YuzWKEE8ZeZvPmv8K\n" +
|
||||||
|
"iJfBoOx59YrlOfpLAKcTR9Ql+IFbWsIkqPxX7U1SGldhfQm7iaK5Dn6+mmQFOz/s\n" +
|
||||||
|
"ZCIavWJ7opsp11JmQAt4FFojv789YswaS7VI1zjDj7EeRiATtzna/GqCYgeCM0cc\n" +
|
||||||
|
"sIe/1j1H2oh9YvYIpPMSGDJPo7T1Ji4Ie3iEQEYNYPuw1Hb7gWYncHXZGJq1nDf/\n" +
|
||||||
|
"WAoI9gSFagpsPW0k9cfEAOVNLNYSyi0CSnQWSjq8THbHKiLPFwsP3hvT2oHycWbK\n" +
|
||||||
|
"u5SfXaTsbMeVQJNdjCNsHq2bOXPGLw==\n" +
|
||||||
|
"=2BW4\n" +
|
||||||
|
"-----END PGP ARMORED FILE-----\n";
|
||||||
|
|
||||||
|
PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(key);
|
||||||
|
PGPSignature t0 = BCUtil.readSignatures(sigT0).get(0);
|
||||||
|
PGPSignature t1t2 = BCUtil.readSignatures(sigT1T2).get(0);
|
||||||
|
PGPSignature t2t3 = BCUtil.readSignatures(sigT2T3).get(0);
|
||||||
|
PGPSignature t3now = BCUtil.readSignatures(sigT3Now).get(0);
|
||||||
|
|
||||||
|
assertThrows(SignatureValidationException.class, () -> SignatureChainValidator.validateSignatureChain(t0,
|
||||||
|
new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8)),
|
||||||
|
publicKeys, PGPainless.getPolicy(), new Date()));
|
||||||
|
assertThrows(SignatureValidationException.class, () -> SignatureChainValidator.validateSignatureChain(t1t2,
|
||||||
|
new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8)),
|
||||||
|
publicKeys, PGPainless.getPolicy(), new Date()));
|
||||||
|
assertThrows(SignatureValidationException.class, () -> SignatureChainValidator.validateSignatureChain(t2t3,
|
||||||
|
new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8)),
|
||||||
|
publicKeys, PGPainless.getPolicy(), new Date()));
|
||||||
|
assertThrows(SignatureValidationException.class, () -> SignatureChainValidator.validateSignatureChain(t3now,
|
||||||
|
new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8)),
|
||||||
|
publicKeys, PGPainless.getPolicy(), new Date()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test signature verification with an evolving signing subkey.
|
||||||
|
*
|
||||||
|
* @see <a href="https://tests.sequoia-pgp.org/#Key_revocation_test__subkey_signs__primary_key_is_not_revoked__base_case_">Sequoia Test-Suite</a>
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void subkeySignsPrimaryKeyNotRevoked() throws IOException, SignatureValidationException {
|
||||||
|
String key = "-----BEGIN PGP ARMORED FILE-----\n" +
|
||||||
|
"Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" +
|
||||||
|
"\n" +
|
||||||
|
"xsBNBFpJegABCAC1ePFquP0135m8DYhcybhv7l+ecojitFOd/jRM7hCczIqKgalD\n" +
|
||||||
|
"1Ro1gNr3VmH6FjRIKIvGT+sOzCKne1v3KyAAPoxtwxjkATTKdOGo15I6v5ZjmO1d\n" +
|
||||||
|
"rLQOLSt1TF7XbQSt+ns6PUZWJL907DvECUU5b9FkNUqfQ14QqY+gi7MOyAQez3b7\n" +
|
||||||
|
"Pg5Cyz/kVWQ6TSMW/myDEDEertQ4rDBsptEDFHCC2+iF4hO2LqfiCriu5qyLcKCQ\n" +
|
||||||
|
"pd6dEuwJQ/jjT0D9A9Fwf+i04x6ZPKSU9oNAWqn8OSAq3/0B/hu9V+0U0iHPnJxe\n" +
|
||||||
|
"quykvJk7maxhiGhxBWYXTvDJmoon0NOles7LABEBAAHCwHwEHwEKAA8Fgl4L4QAC\n" +
|
||||||
|
"FQoCmwMCHgEAIQkQaE+tYtwDj7sWIQTy0VCk/piSXVHpFTloT61i3AOPu8ffB/9Q\n" +
|
||||||
|
"60dg60qhA2rPnd/1dCL2B+c8RWnq44PpijE3gA1RQvcRQE5jNzMSo/MnG0mSL5wH\n" +
|
||||||
|
"eTsjSd/DRI3nHP06rs6Qub11NoKhNuya3maz9gyzeZMc/jNib83/BzFCrxsSQm+9\n" +
|
||||||
|
"WHurxXeWXOPMLZs3xS/jG0EDtCJ2Fm4UF19fcIydwN/ssF4NGpfCY82+wTSx4joI\n" +
|
||||||
|
"3cRKObCFJaaBgG5nl+eFr7cfjEIuqCJCaQsXiqBe7d6V3KqN18t+CgSaybMZXcys\n" +
|
||||||
|
"Q/USxEkLhIB2pOZwcz4E3TTFgxRAxcr4cs4Bd2PRz3Z5FKTzo0ma/Ft0UfFJR+fC\n" +
|
||||||
|
"cs55+n6kC9K0y/E7BY2hwsB8BB8BCgAPBYJaSXoAAhUKApsDAh4BACEJEGhPrWLc\n" +
|
||||||
|
"A4+7FiEE8tFQpP6Ykl1R6RU5aE+tYtwDj7uqDQf7BqTD6GNTwXPOt/0kHQPYmbdI\n" +
|
||||||
|
"tX+pWP+o3jaB6VTHDXcn27bttA5M82EXZfae4+bC1dMB+1uLal4ciVgO9ImJC9Nw\n" +
|
||||||
|
"s5fc3JH4R5uuSvpjzjudkJsGu3cAKE3hwiT93Mi6t6ENpLCDSxqxzAmfoOQbVJYW\n" +
|
||||||
|
"Y7gP7Z4Cj0IAP29aprEc0JWoMjHKpKgYF6u0sWgHWBuEXk/6o6GYb2HZYK4ycpY2\n" +
|
||||||
|
"WXKgVhy7/iQDYO1FOfcWQXHVGLn8OzILjobKohNenTT20ZhAASi3LUDSDMTQfxSS\n" +
|
||||||
|
"Vt0nhzWuXJJ4R8PzUVeRJ0A0oMyjZVHivHC6GwMsiQuSUTx8e/GnOByOqfGne80S\n" +
|
||||||
|
"anVsaWV0QGV4YW1wbGUub3JnwsBzBBMBCgAGBYJaSXoAACEJEGhPrWLcA4+7FiEE\n" +
|
||||||
|
"8tFQpP6Ykl1R6RU5aE+tYtwDj7tDfQf+PnxsIFu/0juKBUjjtAYfRzkrrYtMepPj\n" +
|
||||||
|
"taTvGfo1SzUkX/6F/GjdSeVg5Iq6YcBrj8c+cB3EoZpHnScTgWQHwceWQLd9Hhbg\n" +
|
||||||
|
"TrUNvW1eg2CVzN0RBuYMtWu9JM4pH7ssJW1NmN+/N9B67qb2y+JfBwH/la508NzC\n" +
|
||||||
|
"rl3xWTxjT5wNy+FGkNZg23s/0qlO2uxCjc+mRAuAlp5EmTOVWOIBbM0xttjBOx39\n" +
|
||||||
|
"ZmWWQKJZ0nrFjK1jppHqazwWWNX7RHkK81tlbSUtOPoTIJDz38NaiyMcZH3p9okN\n" +
|
||||||
|
"3DU4XtF+oE18M+Z/E0xUQmumbkajFzcUjmd7enozP5BnGESzdNS5Xc7ATQRaSsuA\n" +
|
||||||
|
"AQgAykb8tqlWXtqHGGkBqAq3EnpmvBqrKvqejjtZKAXqEszJ9NlibCGUuLwnNOVO\n" +
|
||||||
|
"R/hcOUlOGH+cyMcApBWJB+7d/83K1eCCdv88nDFVav7hKLKlEBbZJNHgHpJ313pl\n" +
|
||||||
|
"etzCR4x3STEISrEtO71l2HBdrKSYXaxGgILxYwcSi3i2EjzxRDy+0zyy8s7d+OD5\n" +
|
||||||
|
"ShFYexgSrKH3Xx1cxQAJzGGJVx75HHU9GVh3xHwJ7nDm26KzHegG2XPIBXJ2z8vm\n" +
|
||||||
|
"sSVTWyj0AjT4kVVapN0f84AKKjyQ7fguCzXGHFV9jmxDx+YH+9HhjIrHSzbDx6+4\n" +
|
||||||
|
"wyRsxj7Su+hu/bogJ28nnbTzQwARAQABwsGsBBgBCgAJBYJeC+EAApsCAVcJEGhP\n" +
|
||||||
|
"rWLcA4+7wHSgBBkBCgAGBYJeC+EAACEJEEpyNKOhITplFiEEUXksDkji/alOk7kR\n" +
|
||||||
|
"SnI0o6EhOmWnSQgAiu/zdEmHf6Wbwfbs/c6FObfPxGuzLkQr4fZKcqK81MtR1mh1\n" +
|
||||||
|
"WVLJRgXW4u8cHtZyH5pThngMcUiyzWsa0g6Jaz8w6sr/Wv3e1qdTCITskMrWCDao\n" +
|
||||||
|
"DhD2teAjmWuk9u8ZBPJ7xhme+Q/UQ90xomQ/NdCJafirk2Ds92p7N7RKSES1KywB\n" +
|
||||||
|
"hfONJbPw1TdZ9Mts+DGjkucYbe+ZzPxrLpWXur1BSGEqBtTAGW3dS/xpwBYNlhas\n" +
|
||||||
|
"XHjYMr4HeIYYYOx+oR5JgDYoVfp2k0DwK/QXogbja+/Vjv+LrXdNY0t1bA35FNnl\n" +
|
||||||
|
"637M8iCNrXvIoRFARbNyge8c/jSWGPLB/tIyNhYhBPLRUKT+mJJdUekVOWhPrWLc\n" +
|
||||||
|
"A4+7FLwIAK1GngNMnruxWM4EoghKTSmKNrd6p/d3Wsd+y2019A7Nz+4OydkEDvmN\n" +
|
||||||
|
"VVhlUcfgOf2L6Bf63wdN0ho+ODhCuNSqHe6NL1NhdITbMGnDdKb57IIB9CuJFpIL\n" +
|
||||||
|
"n9LZ1Ei6JPEpmpiSEaL+VJt1fMnfc8jtF8N3WcRVfJsq1aslXe8Npg709YVgm2OX\n" +
|
||||||
|
"sNWgktl9fciu4ENTybQGjpN9WTa1aU1nkko6NUoIfjtM+PO4VU7x00M+dTJsYGhn\n" +
|
||||||
|
"c96EtT8EfSAIFBKZRAkMBFhEcdkxa8hCKI3+nyI3gTq0TcFST3wy05AmoV7wlgzU\n" +
|
||||||
|
"AMsW7MV2NpG7fJul2Q7puKw+udBUc0TCwawEGAEKAAkFglro/4ACmwIBVwkQaE+t\n" +
|
||||||
|
"YtwDj7vAdKAEGQEKAAYFglro/4AAIQkQSnI0o6EhOmUWIQRReSwOSOL9qU6TuRFK\n" +
|
||||||
|
"cjSjoSE6ZeFHB/92jhUTXrEgho6DYhmVFuXa3NGhAjIyZo3yYHMoL9aZ3DUyjxhA\n" +
|
||||||
|
"yRDpI2CrahQ4JsPhej2m+3fHWa34/tb5mpHYFWEahQvdWSFCcU7p2NUKcq2zNA6i\n" +
|
||||||
|
"xO2+fQQhmbrYR+TFxYmhLjCGUNt14E/XaIL1VxPQOA5KbiRPpa8BsUNlNik9ASPW\n" +
|
||||||
|
"yn0ZA0rjJ1ZV7nJarXVbuZDEcUDuDm3cA5tup7juB8fTz2BDcg3Ka+OcPEz0GgZf\n" +
|
||||||
|
"q9K40di3r9IHLBhNPHieFVIj9j/JyMnTvVOceM3J/Rb0MCWJVbXNBKpRMDibCQh+\n" +
|
||||||
|
"7fbqyQEM/zIpmk0TgBpTZZqMP0gxYdWImT1IFiEE8tFQpP6Ykl1R6RU5aE+tYtwD\n" +
|
||||||
|
"j7tOtggAhgAqvOB142L2SkS3ZIdwuhAtWLPHCtEwBOqGtP8Z204rqAmbnJymzo77\n" +
|
||||||
|
"+OT+SScnDTrwzOUJnCi0qPUxfuxhvHxnBxBIjaoMcF++iKsqF1vf6WuXOjbJ1N8I\n" +
|
||||||
|
"08pB2niht5MxIZ9rMGDeASj79X7I9Jjzsd30OVGfTZyy3VyYPxcJ6n/sZocNmaTv\n" +
|
||||||
|
"0/F8K3TirSH6JDXdY5zirRi99GJ3R+AL6OzxrChuvLFSEtIRJrW5XVfg3whc0XD+\n" +
|
||||||
|
"5J9RsHoL33ub9ZhQHFKsjrf0nGYbEFwMhSdysfTYYMbwKi0CcQeQtPP0Y87zSrya\n" +
|
||||||
|
"jDMFXQS0exdvhN4AXDlPlB3Rrkj7CQ==\n" +
|
||||||
|
"=+VTZ\n" +
|
||||||
|
"-----END PGP ARMORED FILE-----\n";
|
||||||
|
String sig = "-----BEGIN PGP ARMORED FILE-----\n" +
|
||||||
|
"Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" +
|
||||||
|
"\n" +
|
||||||
|
"wsBzBAABCgAGBYJdP4iAACEJEEpyNKOhITplFiEEUXksDkji/alOk7kRSnI0o6Eh\n" +
|
||||||
|
"OmUYXQf/dGNZay40bZEpcnxYl+Kq+gRQESeDhg/xOfGfSCLQncMH+UYPaUKANC2g\n" +
|
||||||
|
"CfMNN1wd8ZWrvgyTVo3TVfK1P1RYa9nrvKoKN3bjsFcY6V7VciPW58xVNsuxsEEC\n" +
|
||||||
|
"GEH96TQy+FsP680tRnzQ3Dbw/JT6o6Xi+HLf4JVFceapBgyth61E5gN5w3azxVFr\n" +
|
||||||
|
"GfwIfHvepOjCIq9tRZsRFEBp3XVZ/AF+zQMG5nfIVSm1kVtZjb7KXc3Bj48DVrmb\n" +
|
||||||
|
"XLxPJz7PLY0cgOsXXxROIdtFT+mbVQg2j247hxnhItwtLeQrafb5T8ibeihRlkhK\n" +
|
||||||
|
"1tfKv31EP8tAVqgTjw+qD32bH9h77w==\n" +
|
||||||
|
"=MOaJ\n" +
|
||||||
|
"-----END PGP ARMORED FILE-----\n";
|
||||||
|
|
||||||
|
PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(key);
|
||||||
|
PGPSignature signature = BCUtil.readSignatures(sig).get(0);
|
||||||
|
|
||||||
|
SignatureChainValidator.validateSignatureChain(signature,
|
||||||
|
new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8)),
|
||||||
|
publicKeys, PGPainless.getPolicy(), new Date());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,136 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 Paul Schaub.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.pgpainless.signature;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.pgpainless.PGPainless;
|
||||||
|
import org.pgpainless.algorithm.HashAlgorithm;
|
||||||
|
import org.pgpainless.key.KeyRingValidator;
|
||||||
|
import org.pgpainless.policy.Policy;
|
||||||
|
import org.pgpainless.util.ArmorUtils;
|
||||||
|
import org.pgpainless.util.TestUtils;
|
||||||
|
|
||||||
|
public class KeyRingValidationTest {
|
||||||
|
|
||||||
|
private static Policy.HashAlgorithmPolicy defaultSignatureHashAlgorithmPolicy;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
public static void setCustomPolicy() {
|
||||||
|
Policy policy = PGPainless.getPolicy();
|
||||||
|
defaultSignatureHashAlgorithmPolicy = policy.getSignatureHashAlgorithmPolicy();
|
||||||
|
|
||||||
|
policy.setSignatureHashAlgorithmPolicy(new Policy.HashAlgorithmPolicy(HashAlgorithm.SHA256, Collections.singletonList(HashAlgorithm.SHA256)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
public static void resetCustomPolicy() {
|
||||||
|
PGPainless.getPolicy().setSignatureHashAlgorithmPolicy(defaultSignatureHashAlgorithmPolicy);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSignatureValidationOnPrimaryKey() throws IOException, PGPException {
|
||||||
|
String key = "-----BEGIN PGP ARMORED FILE-----\n" +
|
||||||
|
"Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" +
|
||||||
|
"\n" +
|
||||||
|
"xsBNBFpJegABCAC1ePFquP0135m8DYhcybhv7l+ecojitFOd/jRM7hCczIqKgalD\n" +
|
||||||
|
"1Ro1gNr3VmH6FjRIKIvGT+sOzCKne1v3KyAAPoxtwxjkATTKdOGo15I6v5ZjmO1d\n" +
|
||||||
|
"rLQOLSt1TF7XbQSt+ns6PUZWJL907DvECUU5b9FkNUqfQ14QqY+gi7MOyAQez3b7\n" +
|
||||||
|
"Pg5Cyz/kVWQ6TSMW/myDEDEertQ4rDBsptEDFHCC2+iF4hO2LqfiCriu5qyLcKCQ\n" +
|
||||||
|
"pd6dEuwJQ/jjT0D9A9Fwf+i04x6ZPKSU9oNAWqn8OSAq3/0B/hu9V+0U0iHPnJxe\n" +
|
||||||
|
"quykvJk7maxhiGhxBWYXTvDJmoon0NOles7LABEBAAHCwIcEIAEKABoFglwqrYAT\n" +
|
||||||
|
"HQFLZXkgaXMgc3VwZXJzZWRlZAAhCRBoT61i3AOPuxYhBPLRUKT+mJJdUekVOWhP\n" +
|
||||||
|
"rWLcA4+76+wH/1NmN/Qma5FTxmSWEcfH2ynKhwejKp8p8O7+y/uq1FlUwRzChzeX\n" +
|
||||||
|
"kd9w099uODMasxGaNSJU1mh5N+1oulyHrSyWFRWqDnQUnDx3IiPapK/j85udkJdo\n" +
|
||||||
|
"WfdTcxaS2C9Yo4S77cPwkbFLmEQ2Ovs5zjj0Q+mfoZNM+KJcsnOoJ+eeOE2GNA3x\n" +
|
||||||
|
"5TWvw0QXBfyW74MZHc0UE82ixcG6g4KbrI6W544EixY5vu3IxVsxiL66zy27A8ha\n" +
|
||||||
|
"EDdBWS8kc8UQ2cRveuqZwRsWcrh/2iHHShY/5zBOdQ1PL++ubwkteNSU9SsXjjDM\n" +
|
||||||
|
"oWm1RGy7/bagPPtqBnRMQ20vvW+3oBYxyd7CwHwEHwEKAA8Fgl4L4QACFQoCmwMC\n" +
|
||||||
|
"HgEAIQkQaE+tYtwDj7sWIQTy0VCk/piSXVHpFTloT61i3AOPu8ffB/9Q60dg60qh\n" +
|
||||||
|
"A2rPnd/1dCL2B+c8RWnq44PpijE3gA1RQvcRQE5jNzMSo/MnG0mSL5wHeTsjSd/D\n" +
|
||||||
|
"RI3nHP06rs6Qub11NoKhNuya3maz9gyzeZMc/jNib83/BzFCrxsSQm+9WHurxXeW\n" +
|
||||||
|
"XOPMLZs3xS/jG0EDtCJ2Fm4UF19fcIydwN/ssF4NGpfCY82+wTSx4joI3cRKObCF\n" +
|
||||||
|
"JaaBgG5nl+eFr7cfjEIuqCJCaQsXiqBe7d6V3KqN18t+CgSaybMZXcysQ/USxEkL\n" +
|
||||||
|
"hIB2pOZwcz4E3TTFgxRAxcr4cs4Bd2PRz3Z5FKTzo0ma/Ft0UfFJR+fCcs55+n6k\n" +
|
||||||
|
"C9K0y/E7BY2hwsB8BB8BCgAPBYJaSXoAAhUKApsDAh4BACEJEGhPrWLcA4+7FiEE\n" +
|
||||||
|
"8tFQpP6Ykl1R6RU5aE+tYtwDj7uqDQf7BqTD6GNTwXPOt/0kHQPYmbdItX+pWP+o\n" +
|
||||||
|
"3jaB6VTHDXcn27bttA5M82EXZfae4+bC1dMB+1uLal4ciVgO9ImJC9Nws5fc3JH4\n" +
|
||||||
|
"R5uuSvpjzjudkJsGu3cAKE3hwiT93Mi6t6ENpLCDSxqxzAmfoOQbVJYWY7gP7Z4C\n" +
|
||||||
|
"j0IAP29aprEc0JWoMjHKpKgYF6u0sWgHWBuEXk/6o6GYb2HZYK4ycpY2WXKgVhy7\n" +
|
||||||
|
"/iQDYO1FOfcWQXHVGLn8OzILjobKohNenTT20ZhAASi3LUDSDMTQfxSSVt0nhzWu\n" +
|
||||||
|
"XJJ4R8PzUVeRJ0A0oMyjZVHivHC6GwMsiQuSUTx8e/GnOByOqfGne80SanVsaWV0\n" +
|
||||||
|
"QGV4YW1wbGUub3JnwsBzBBMBCgAGBYJaSXoAACEJEGhPrWLcA4+7FiEE8tFQpP6Y\n" +
|
||||||
|
"kl1R6RU5aE+tYtwDj7tDfQf+PnxsIFu/0juKBUjjtAYfRzkrrYtMepPjtaTvGfo1\n" +
|
||||||
|
"SzUkX/6F/GjdSeVg5Iq6YcBrj8c+cB3EoZpHnScTgWQHwceWQLd9HhbgTrUNvW1e\n" +
|
||||||
|
"g2CVzN0RBuYMtWu9JM4pH7ssJW1NmN+/N9B67qb2y+JfBwH/la508NzCrl3xWTxj\n" +
|
||||||
|
"T5wNy+FGkNZg23s/0qlO2uxCjc+mRAuAlp5EmTOVWOIBbM0xttjBOx39ZmWWQKJZ\n" +
|
||||||
|
"0nrFjK1jppHqazwWWNX7RHkK81tlbSUtOPoTIJDz38NaiyMcZH3p9okN3DU4XtF+\n" +
|
||||||
|
"oE18M+Z/E0xUQmumbkajFzcUjmd7enozP5BnGESzdNS5Xc7ATQRaSsuAAQgAykb8\n" +
|
||||||
|
"tqlWXtqHGGkBqAq3EnpmvBqrKvqejjtZKAXqEszJ9NlibCGUuLwnNOVOR/hcOUlO\n" +
|
||||||
|
"GH+cyMcApBWJB+7d/83K1eCCdv88nDFVav7hKLKlEBbZJNHgHpJ313pletzCR4x3\n" +
|
||||||
|
"STEISrEtO71l2HBdrKSYXaxGgILxYwcSi3i2EjzxRDy+0zyy8s7d+OD5ShFYexgS\n" +
|
||||||
|
"rKH3Xx1cxQAJzGGJVx75HHU9GVh3xHwJ7nDm26KzHegG2XPIBXJ2z8vmsSVTWyj0\n" +
|
||||||
|
"AjT4kVVapN0f84AKKjyQ7fguCzXGHFV9jmxDx+YH+9HhjIrHSzbDx6+4wyRsxj7S\n" +
|
||||||
|
"u+hu/bogJ28nnbTzQwARAQABwsGsBBgBCgAJBYJeC+EAApsCAVcJEGhPrWLcA4+7\n" +
|
||||||
|
"wHSgBBkBCgAGBYJeC+EAACEJEEpyNKOhITplFiEEUXksDkji/alOk7kRSnI0o6Eh\n" +
|
||||||
|
"OmWnSQgAiu/zdEmHf6Wbwfbs/c6FObfPxGuzLkQr4fZKcqK81MtR1mh1WVLJRgXW\n" +
|
||||||
|
"4u8cHtZyH5pThngMcUiyzWsa0g6Jaz8w6sr/Wv3e1qdTCITskMrWCDaoDhD2teAj\n" +
|
||||||
|
"mWuk9u8ZBPJ7xhme+Q/UQ90xomQ/NdCJafirk2Ds92p7N7RKSES1KywBhfONJbPw\n" +
|
||||||
|
"1TdZ9Mts+DGjkucYbe+ZzPxrLpWXur1BSGEqBtTAGW3dS/xpwBYNlhasXHjYMr4H\n" +
|
||||||
|
"eIYYYOx+oR5JgDYoVfp2k0DwK/QXogbja+/Vjv+LrXdNY0t1bA35FNnl637M8iCN\n" +
|
||||||
|
"rXvIoRFARbNyge8c/jSWGPLB/tIyNhYhBPLRUKT+mJJdUekVOWhPrWLcA4+7FLwI\n" +
|
||||||
|
"AK1GngNMnruxWM4EoghKTSmKNrd6p/d3Wsd+y2019A7Nz+4OydkEDvmNVVhlUcfg\n" +
|
||||||
|
"Of2L6Bf63wdN0ho+ODhCuNSqHe6NL1NhdITbMGnDdKb57IIB9CuJFpILn9LZ1Ei6\n" +
|
||||||
|
"JPEpmpiSEaL+VJt1fMnfc8jtF8N3WcRVfJsq1aslXe8Npg709YVgm2OXsNWgktl9\n" +
|
||||||
|
"fciu4ENTybQGjpN9WTa1aU1nkko6NUoIfjtM+PO4VU7x00M+dTJsYGhnc96EtT8E\n" +
|
||||||
|
"fSAIFBKZRAkMBFhEcdkxa8hCKI3+nyI3gTq0TcFST3wy05AmoV7wlgzUAMsW7MV2\n" +
|
||||||
|
"NpG7fJul2Q7puKw+udBUc0TCwawEGAEKAAkFglro/4ACmwIBVwkQaE+tYtwDj7vA\n" +
|
||||||
|
"dKAEGQEKAAYFglro/4AAIQkQSnI0o6EhOmUWIQRReSwOSOL9qU6TuRFKcjSjoSE6\n" +
|
||||||
|
"ZeFHB/92jhUTXrEgho6DYhmVFuXa3NGhAjIyZo3yYHMoL9aZ3DUyjxhAyRDpI2Cr\n" +
|
||||||
|
"ahQ4JsPhej2m+3fHWa34/tb5mpHYFWEahQvdWSFCcU7p2NUKcq2zNA6ixO2+fQQh\n" +
|
||||||
|
"mbrYR+TFxYmhLjCGUNt14E/XaIL1VxPQOA5KbiRPpa8BsUNlNik9ASPWyn0ZA0rj\n" +
|
||||||
|
"J1ZV7nJarXVbuZDEcUDuDm3cA5tup7juB8fTz2BDcg3Ka+OcPEz0GgZfq9K40di3\n" +
|
||||||
|
"r9IHLBhNPHieFVIj9j/JyMnTvVOceM3J/Rb0MCWJVbXNBKpRMDibCQh+7fbqyQEM\n" +
|
||||||
|
"/zIpmk0TgBpTZZqMP0gxYdWImT1IFiEE8tFQpP6Ykl1R6RU5aE+tYtwDj7tOtggA\n" +
|
||||||
|
"hgAqvOB142L2SkS3ZIdwuhAtWLPHCtEwBOqGtP8Z204rqAmbnJymzo77+OT+SScn\n" +
|
||||||
|
"DTrwzOUJnCi0qPUxfuxhvHxnBxBIjaoMcF++iKsqF1vf6WuXOjbJ1N8I08pB2nih\n" +
|
||||||
|
"t5MxIZ9rMGDeASj79X7I9Jjzsd30OVGfTZyy3VyYPxcJ6n/sZocNmaTv0/F8K3Ti\n" +
|
||||||
|
"rSH6JDXdY5zirRi99GJ3R+AL6OzxrChuvLFSEtIRJrW5XVfg3whc0XD+5J9RsHoL\n" +
|
||||||
|
"33ub9ZhQHFKsjrf0nGYbEFwMhSdysfTYYMbwKi0CcQeQtPP0Y87zSryajDMFXQS0\n" +
|
||||||
|
"exdvhN4AXDlPlB3Rrkj7CQ==\n" +
|
||||||
|
"=qQpG\n" +
|
||||||
|
"-----END PGP ARMORED FILE-----\n";
|
||||||
|
|
||||||
|
PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(key);
|
||||||
|
|
||||||
|
Date validationDate = TestUtils.getUTCDate("2019-05-01 00:00:00 UTC");
|
||||||
|
Policy policy = PGPainless.getPolicy();
|
||||||
|
PGPPublicKeyRing evaluated = KeyRingValidator.validate(publicKeys, policy, validationDate);
|
||||||
|
|
||||||
|
// CHECKSTYLE:OFF
|
||||||
|
System.out.println(ArmorUtils.toAsciiArmoredString(evaluated));
|
||||||
|
// CHECKSTYLE:ON
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load diff
|
@ -15,22 +15,16 @@
|
||||||
*/
|
*/
|
||||||
package org.pgpainless.util;
|
package org.pgpainless.util;
|
||||||
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
public class NotationRegistryTest {
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
@BeforeEach
|
public class NotationRegistryTest {
|
||||||
public void setup() {
|
|
||||||
NotationRegistry.getInstance().clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void notationIsKnownOnceAddedAndUnknownOnceCleared() {
|
public void notationIsKnownOnceAddedAndUnknownOnceCleared() {
|
||||||
NotationRegistry registry = NotationRegistry.getInstance();
|
NotationRegistry registry = new NotationRegistry();
|
||||||
|
|
||||||
assertFalse(registry.isKnownNotation("proof@metacode.biz"), "Notation is initially not known.");
|
assertFalse(registry.isKnownNotation("proof@metacode.biz"), "Notation is initially not known.");
|
||||||
assertFalse(registry.isKnownNotation("unkown@notation.data"));
|
assertFalse(registry.isKnownNotation("unkown@notation.data"));
|
||||||
|
|
|
@ -26,6 +26,7 @@ import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
|
||||||
import org.bouncycastle.openpgp.PGPSignatureSubpacketVector;
|
import org.bouncycastle.openpgp.PGPSignatureSubpacketVector;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.pgpainless.algorithm.SignatureSubpacket;
|
import org.pgpainless.algorithm.SignatureSubpacket;
|
||||||
|
import org.pgpainless.signature.subpackets.SignatureSubpacketGeneratorUtil;
|
||||||
|
|
||||||
public class SignatureSubpacketGeneratorUtilTest {
|
public class SignatureSubpacketGeneratorUtilTest {
|
||||||
|
|
||||||
|
|
|
@ -15,10 +15,23 @@
|
||||||
*/
|
*/
|
||||||
package org.pgpainless.util;
|
package org.pgpainless.util;
|
||||||
|
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
|
||||||
public class TestUtils {
|
public class TestUtils {
|
||||||
|
|
||||||
|
public static SimpleDateFormat UTC_PARSER = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
|
||||||
|
|
||||||
|
public static Date getUTCDate(String dateString) {
|
||||||
|
try {
|
||||||
|
return UTC_PARSER.parse(dateString);
|
||||||
|
} catch (ParseException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static int getNumberOfItemsInIterator(Iterator<?> iterator) {
|
public static int getNumberOfItemsInIterator(Iterator<?> iterator) {
|
||||||
int num = 0;
|
int num = 0;
|
||||||
while (iterator.hasNext()) {
|
while (iterator.hasNext()) {
|
||||||
|
|
|
@ -0,0 +1,186 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 Paul Schaub.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.pgpainless.util.selection.signature;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||||
|
import org.bouncycastle.openpgp.PGPSignature;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.pgpainless.PGPainless;
|
||||||
|
import org.pgpainless.signature.SelectSignatureFromKey;
|
||||||
|
|
||||||
|
public class SelectSignatureFromKeyTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void validKeyTest() throws IOException, PGPException {
|
||||||
|
String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
|
||||||
|
"\n" +
|
||||||
|
"xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" +
|
||||||
|
"/seOXpgecTdOcVttfzC8ycIKrt3aQTiwOG/ctaR4Bk/t6ayNFfdUNxHWk4WCKzdz\n" +
|
||||||
|
"/56fW2O0F23qIRd8UUJp5IIlN4RDdRCtdhVQIAuzvp2oVy/LaS2kxQoKvph/5pQ/\n" +
|
||||||
|
"5whqsyroEWDJoSV0yOb25B/iwk/pLUFoyhDG9bj0kIzDxrEqW+7Ba8nocQlecMF3\n" +
|
||||||
|
"X5KMN5kp2zraLv9dlBBpWW43XktjcCZgMy20SouraVma8Je/ECwUWYUiAZxLIlMv\n" +
|
||||||
|
"9CurEOtxUw6N3RdOtLmYZS9uEnn5y1UkF88o8Nku890uk6BrewFzJyLAx5wRZ4F0\n" +
|
||||||
|
"qV/yq36UWQ0JB/AUGhHVPdFf6pl6eaxBwT5GXvbBUibtf8YI2og5RsgTWtXfU7eb\n" +
|
||||||
|
"SGXrl5ZMpbA6mbfhd0R8aPxWfmDWiIOhBufhMCvUHh1sApMKVZnvIff9/0Dca3wb\n" +
|
||||||
|
"vLIwa3T4CyshfT0AEQEAAc0hQm9iIEJhYmJhZ2UgPGJvYkBvcGVucGdwLmV4YW1w\n" +
|
||||||
|
"bGU+wsFIBBMBCgB8BYJfRGs6AgsJCRD7/MgqAV5zMEcUAAAAAAAeACBzYWx0QG5v\n" +
|
||||||
|
"dGF0aW9ucy5zZXF1b2lhLXBncC5vcmfG4smOBDeAPqApuhtNx1qTvcbgFVo/gKVD\n" +
|
||||||
|
"bmy8y8ocOwMVCAoCmwECHgEWIQTRpm4aI7GCyZgPeIz7/MgqAV5zMAAA/zwMAKD9\n" +
|
||||||
|
"skJhBHzBg0KJKwyaILWlXItDm0Np9GAWTzRa1HWwy4oLzM5tVdi5UiQOO7wsY3r5\n" +
|
||||||
|
"NMpkwZrlf7xJzn1lXuonUW3GN/L4MlE8SjjXwvwo7HHDijRa3bs6w6xFi4O21WUL\n" +
|
||||||
|
"mi3cwZU0KvGTygW9iTW4bG92KqdejZzyPnJJlmhqhS0rUFKIwGW9OIvIKUmeeeBH\n" +
|
||||||
|
"/0zTQBO0zErC73FRekyPTfR3ePuHZ/2VMnd4gI5sBrx9rOLBN/mGU9tBsEAd5Fo0\n" +
|
||||||
|
"X0Wgdcm1N7NNcseC0rKFfGjvEah9r/U5NryGjseMPRd+HgogGvuCsAfBcQc4EgbP\n" +
|
||||||
|
"4a0aNlrOqJObyOxkOrYofI2f9l0UgHngskF6bTL+LHQ7H49L+gCzbIXJVytHOh+U\n" +
|
||||||
|
"7povgQM3OMhG3zNGvxhqgr//k4mDb7G4ygTCOi8lklxkOK/jT3qNHgkoXOWBhKet\n" +
|
||||||
|
"AH3aeKnfoChPO/YtZvyZWPW8RcgZkDmyvFyuAuee3YeQbMy4nj2hdgaxYgJ4rs7A\n" +
|
||||||
|
"zQRdpZzyAQwA1jC/XGxjK6ddgrRfW9j+s/U00++EvIsgTs2kr3Rg0GP7FLWV0YNt\n" +
|
||||||
|
"R1mpl55/bEl7yAxCDTkOgPUMXcaKlnQh6zrlt6H53mF6Bvs3inOHQvOsGtU0dqvb\n" +
|
||||||
|
"1vkTF0juLiJgPlM7pWv+pNQ6IA39vKoQsTMBv4v5vYNXP9GgKbg8inUNT17BxzZY\n" +
|
||||||
|
"Hfw5+q63ectgDm2on1e8CIRCZ76oBVwzdkVxoy3gjh1eENlk2D4P0uJNZzF1Q8GV\n" +
|
||||||
|
"67yLANGMCDICE/OkWn6daipYDzW4iJQtYPUWP4hWhjdm+CK+hg6IQUEn2Vtvi16D\n" +
|
||||||
|
"2blRP8BpUNNa4fNuylWVuJV76rIHvsLZ1pbM3LHpRgE8s6jivS3Rz3WRs0TmWCNn\n" +
|
||||||
|
"vHPqWizQ3VTy+r3UQVJ5AmhJDrZdZq9iaUIuZ01PoE1+CHiJwuxPtWvVAxf2POcm\n" +
|
||||||
|
"1M/F1fK1J0e+lKlQuyonTXqXR22Y41wrfP2aPk3nPSTW2DUAf3vRMZg57ZpRxLEh\n" +
|
||||||
|
"EMxcM4/LMR+PABEBAAHCwzwEGAEKAnAFgl9EazoJEPv8yCoBXnMwRxQAAAAAAB4A\n" +
|
||||||
|
"IHNhbHRAbm90YXRpb25zLnNlcXVvaWEtcGdwLm9yZ0dWWutVYwZr+KCx8xhv5NSk\n" +
|
||||||
|
"pCq2a216Tlbw6NswPnv8ApsCwTygBBkBCgBvBYJfRGs6CRB8L6pN+Tw3skcUAAAA\n" +
|
||||||
|
"AAAeACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmcLUKz5boYqjMRAhrIx\n" +
|
||||||
|
"mpikklkNAkNvfSAj/8aFUlIYghYhBB3c4V8JIXzuLzs3YHwvqk35PDeyAAB1wAv/\n" +
|
||||||
|
"VGqUIlfFGTGdfraSJ9yqxoCaxmWHtIkwPPVxUcrS/DQaiLd0Bc2tah9f9VHE0wCj\n" +
|
||||||
|
"Db7pzk2vugYKrebvskFQaq0S8TwhHQ4n9GVrUnenFf2OAWYfRYmYbENUv+fQm22+\n" +
|
||||||
|
"EOxHWSVwB3NWl8albQxs/aPCi3nuPdtdTMU2fHLGDAZ9MGQesb/0tSJLWaqQRvqT\n" +
|
||||||
|
"k3llI1OqxGbYLaNXSz6nJDLsKK9v+6lFzxA5C8OOxGikHE7b9RJ6SGVNijItXtHo\n" +
|
||||||
|
"rVuAKayDfMKO+0jc25I+agMbfg6p4Ik5D+1LFzZtsSc6Ib6AKu+FLit6Ik74/nrr\n" +
|
||||||
|
"/ORSAoTpxnIyJlBu4DS3AUwRd/O7rke8FNVg6EpzaPazrqfY1eZ2YelEE4EO3xXm\n" +
|
||||||
|
"wcOLSPVwsLNoC3DdRRLtw5EItZy2z0QiARF+NsUYQQM5RCrQizxuzD5+nXg1AcaE\n" +
|
||||||
|
"ixnbju8StB8jT1m4ccJKHsObgi/cIPPsWm5+BUhV9RDLsMWnaVZ8f3tRAHy2TAld\n" +
|
||||||
|
"FiEE0aZuGiOxgsmYD3iM+/zIKgFeczAAAAv/DACScy69f/qohzub6e06b3sgmL1K\n" +
|
||||||
|
"foCMmFRAiEsDHUHunAb/KWBqkbJ8W6wP0COwh4tbmjUzwexMQyI4m58SLRYULcJ7\n" +
|
||||||
|
"kj3axMV0+JJyFoqUpCT06GpqQQIhZY7Y+AHz9FdVNEDjjUwb3mODx8zVyEg57T9C\n" +
|
||||||
|
"TfuLrrJDYpycfNJtxYy9qSMPHBiVGqlzqnyETOa312QquZuY6ucfTL8i8kXk5qtL\n" +
|
||||||
|
"jVHTnKogzrbTCWuKR8fzsxfZ9afdYXI3SMMsip4Ixx2mLM5tN9IeDI/DQnWetwB2\n" +
|
||||||
|
"Z0PEs7UcYcrn6UWs1X4P7jOmtLH+0d96I9ljd9SSmJ9dTr2cV62J/qtK+75hCBk8\n" +
|
||||||
|
"Lz+MNWzyAU3sVqGRhsBaLOqvb7K9p3bm6brEmGpBLeKrxuxjBER+7knqkTxSsb+S\n" +
|
||||||
|
"msO3lGrEnNEQIlcvoxLIGQiv9b0sblGM9lr40C0D84PEvajhuFAUTItoPfCIVVaT\n" +
|
||||||
|
"7Ry8/ZA6t0uQh9/B0hYblb07mJ92hCacoTx+APM=\n" +
|
||||||
|
"=yeYe\n" +
|
||||||
|
"-----END PGP PUBLIC KEY BLOCK-----";
|
||||||
|
|
||||||
|
PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(key);
|
||||||
|
Iterator<PGPPublicKey> keyIt = publicKeys.getPublicKeys();
|
||||||
|
PGPPublicKey primaryKey = publicKeys.getPublicKey();
|
||||||
|
while (keyIt.hasNext()) {
|
||||||
|
PGPPublicKey publicKey = keyIt.next();
|
||||||
|
// CHECKSTYLE:OFF
|
||||||
|
System.out.println(publicKey.getKeyID());
|
||||||
|
// CHECKSTYLE:ON
|
||||||
|
|
||||||
|
Iterator<PGPSignature> signatures = publicKey.getSignatures();
|
||||||
|
while (signatures.hasNext()) {
|
||||||
|
PGPSignature signature = signatures.next();
|
||||||
|
if (SelectSignatureFromKey.isValidSubkeyBindingSignature(primaryKey, publicKey).accept(signature, publicKey, publicKeys)) {
|
||||||
|
// CHECKSTYLE:OFF
|
||||||
|
System.out.println("Valid subkey binding signature");
|
||||||
|
// CHECKSTYLE:ON
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void missingBackSigTest() throws IOException {
|
||||||
|
String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
|
||||||
|
"\n" +
|
||||||
|
"xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" +
|
||||||
|
"/seOXpgecTdOcVttfzC8ycIKrt3aQTiwOG/ctaR4Bk/t6ayNFfdUNxHWk4WCKzdz\n" +
|
||||||
|
"/56fW2O0F23qIRd8UUJp5IIlN4RDdRCtdhVQIAuzvp2oVy/LaS2kxQoKvph/5pQ/\n" +
|
||||||
|
"5whqsyroEWDJoSV0yOb25B/iwk/pLUFoyhDG9bj0kIzDxrEqW+7Ba8nocQlecMF3\n" +
|
||||||
|
"X5KMN5kp2zraLv9dlBBpWW43XktjcCZgMy20SouraVma8Je/ECwUWYUiAZxLIlMv\n" +
|
||||||
|
"9CurEOtxUw6N3RdOtLmYZS9uEnn5y1UkF88o8Nku890uk6BrewFzJyLAx5wRZ4F0\n" +
|
||||||
|
"qV/yq36UWQ0JB/AUGhHVPdFf6pl6eaxBwT5GXvbBUibtf8YI2og5RsgTWtXfU7eb\n" +
|
||||||
|
"SGXrl5ZMpbA6mbfhd0R8aPxWfmDWiIOhBufhMCvUHh1sApMKVZnvIff9/0Dca3wb\n" +
|
||||||
|
"vLIwa3T4CyshfT0AEQEAAc0hQm9iIEJhYmJhZ2UgPGJvYkBvcGVucGdwLmV4YW1w\n" +
|
||||||
|
"bGU+wsFIBBMBCgB8BYJfRGs6AgsJCRD7/MgqAV5zMEcUAAAAAAAeACBzYWx0QG5v\n" +
|
||||||
|
"dGF0aW9ucy5zZXF1b2lhLXBncC5vcmfG4smOBDeAPqApuhtNx1qTvcbgFVo/gKVD\n" +
|
||||||
|
"bmy8y8ocOwMVCAoCmwECHgEWIQTRpm4aI7GCyZgPeIz7/MgqAV5zMAAA/zwMAKD9\n" +
|
||||||
|
"skJhBHzBg0KJKwyaILWlXItDm0Np9GAWTzRa1HWwy4oLzM5tVdi5UiQOO7wsY3r5\n" +
|
||||||
|
"NMpkwZrlf7xJzn1lXuonUW3GN/L4MlE8SjjXwvwo7HHDijRa3bs6w6xFi4O21WUL\n" +
|
||||||
|
"mi3cwZU0KvGTygW9iTW4bG92KqdejZzyPnJJlmhqhS0rUFKIwGW9OIvIKUmeeeBH\n" +
|
||||||
|
"/0zTQBO0zErC73FRekyPTfR3ePuHZ/2VMnd4gI5sBrx9rOLBN/mGU9tBsEAd5Fo0\n" +
|
||||||
|
"X0Wgdcm1N7NNcseC0rKFfGjvEah9r/U5NryGjseMPRd+HgogGvuCsAfBcQc4EgbP\n" +
|
||||||
|
"4a0aNlrOqJObyOxkOrYofI2f9l0UgHngskF6bTL+LHQ7H49L+gCzbIXJVytHOh+U\n" +
|
||||||
|
"7povgQM3OMhG3zNGvxhqgr//k4mDb7G4ygTCOi8lklxkOK/jT3qNHgkoXOWBhKet\n" +
|
||||||
|
"AH3aeKnfoChPO/YtZvyZWPW8RcgZkDmyvFyuAuee3YeQbMy4nj2hdgaxYgJ4rs7A\n" +
|
||||||
|
"zQRdpZzyAQwA1jC/XGxjK6ddgrRfW9j+s/U00++EvIsgTs2kr3Rg0GP7FLWV0YNt\n" +
|
||||||
|
"R1mpl55/bEl7yAxCDTkOgPUMXcaKlnQh6zrlt6H53mF6Bvs3inOHQvOsGtU0dqvb\n" +
|
||||||
|
"1vkTF0juLiJgPlM7pWv+pNQ6IA39vKoQsTMBv4v5vYNXP9GgKbg8inUNT17BxzZY\n" +
|
||||||
|
"Hfw5+q63ectgDm2on1e8CIRCZ76oBVwzdkVxoy3gjh1eENlk2D4P0uJNZzF1Q8GV\n" +
|
||||||
|
"67yLANGMCDICE/OkWn6daipYDzW4iJQtYPUWP4hWhjdm+CK+hg6IQUEn2Vtvi16D\n" +
|
||||||
|
"2blRP8BpUNNa4fNuylWVuJV76rIHvsLZ1pbM3LHpRgE8s6jivS3Rz3WRs0TmWCNn\n" +
|
||||||
|
"vHPqWizQ3VTy+r3UQVJ5AmhJDrZdZq9iaUIuZ01PoE1+CHiJwuxPtWvVAxf2POcm\n" +
|
||||||
|
"1M/F1fK1J0e+lKlQuyonTXqXR22Y41wrfP2aPk3nPSTW2DUAf3vRMZg57ZpRxLEh\n" +
|
||||||
|
"EMxcM4/LMR+PABEBAAHCwT4EGAEKAHIFgl9EazoJEPv8yCoBXnMwRxQAAAAAAB4A\n" +
|
||||||
|
"IHNhbHRAbm90YXRpb25zLnNlcXVvaWEtcGdwLm9yZzralcLYPPn2+y5wW/nUhKkM\n" +
|
||||||
|
"7cEGJPF1O2wGnOpPUWjdApsCFiEE0aZuGiOxgsmYD3iM+/zIKgFeczAAALEgC/wL\n" +
|
||||||
|
"sBjuZAnyh0Pdz2srlUdsp3UKgLo8d32QC5/6nd7SY4WSlfbtSDxcyXt9qbi6dN85\n" +
|
||||||
|
"S72cyWfxo2NB8Bi0br/qOuiPcctRxOqrRUye+gQd/9Hd/m/ZmzrTRdqBNAwcQaHE\n" +
|
||||||
|
"DRauKwFbvmkK5P/r1W6PfmXYxQ7ORbQhdI74sOZsKoqfkfEhQJd7StjFA1Y+90hG\n" +
|
||||||
|
"VQbNuWfp+xJSKc2rilqAt73yt8VJtO7Z/aF6Pw8CxzR7Jj2GfFmrWrfw7GR+jLll\n" +
|
||||||
|
"S2QLVQ8/dWfzzv1WTW3c/54dEfz5/vvnLYJB5mUwqXYPF+8gFA0fPA8VdHos/WxL\n" +
|
||||||
|
"PfmPe8LxOoS5GHhilfCil9OfDWtb+PdSXQnfRobOjOjzocw7F+eQLWbTTc4FGWTF\n" +
|
||||||
|
"UI4yNTzgCY2xtivxu7UpPY2ooD7JlmuzrO7TdC8fhj+l/TEgH67wbhhJgFLoDbwA\n" +
|
||||||
|
"+UkgjAOwJ2Rs4Dv77B9o4HUh2Irn72cHy/UsNxkJgoSEkTb30bJJyNlEnds/qyw=\n" +
|
||||||
|
"=uSRw\n" +
|
||||||
|
"-----END PGP PUBLIC KEY BLOCK-----\n";
|
||||||
|
PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(key);
|
||||||
|
Iterator<PGPPublicKey> keyIt = publicKeys.getPublicKeys();
|
||||||
|
PGPPublicKey primaryKey = publicKeys.getPublicKey();
|
||||||
|
while (keyIt.hasNext()) {
|
||||||
|
PGPPublicKey publicKey = keyIt.next();
|
||||||
|
// CHECKSTYLE:OFF
|
||||||
|
System.out.println(publicKey.getKeyID());
|
||||||
|
// CHECKSTYLE:ON
|
||||||
|
if (publicKey.isMasterKey()) {
|
||||||
|
Iterator<PGPSignature> signatures = publicKey.getSignatures();
|
||||||
|
boolean isValidPrimaryKey = false;
|
||||||
|
boolean isRevokedPrimaryKey = false;
|
||||||
|
while (signatures.hasNext()) {
|
||||||
|
PGPSignature signature = signatures.next();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Iterator<PGPSignature> signatures = publicKey.getSignatures();
|
||||||
|
while (signatures.hasNext()) {
|
||||||
|
PGPSignature signature = signatures.next();
|
||||||
|
|
||||||
|
if (SelectSignatureFromKey.isValidSubkeyBindingSignature(primaryKey, publicKey).accept(signature, publicKey, publicKeys)) {
|
||||||
|
// CHECKSTYLE:OFF
|
||||||
|
System.out.println("Valid subkey binding signature");
|
||||||
|
// CHECKSTYLE:ON
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue