2021-10-07 15:48:52 +02:00
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
2021-11-03 13:30:16 +01:00
package org.pgpainless.signature.consumer ;
2021-04-26 13:38:12 +02:00
2021-11-03 13:30:16 +01:00
import static org.pgpainless.signature.consumer.SignatureVerifier.verifyOnePassSignature ;
2021-08-18 12:55:24 +02:00
2021-04-26 13:38:12 +02:00
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 ;
2021-05-25 16:25:22 +02:00
import org.bouncycastle.bcpg.sig.KeyFlags ;
2021-05-03 13:37:47 +02:00
import org.bouncycastle.bcpg.sig.SignerUserID ;
2021-04-26 13:38:12 +02:00
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 ;
2021-04-30 10:23:12 +02:00
import org.pgpainless.exception.SignatureValidationException ;
2021-04-26 13:38:12 +02:00
import org.pgpainless.policy.Policy ;
2021-11-03 13:30:16 +01:00
import org.pgpainless.signature.SignatureUtils ;
2021-04-26 13:38:12 +02:00
import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil ;
2021-08-23 13:29:57 +02:00
import org.slf4j.Logger ;
import org.slf4j.LoggerFactory ;
2021-04-26 13:38:12 +02:00
2021-05-03 13:37:47 +02:00
/ * *
2021-08-18 14:28:36 +02:00
* A collection of static methods that validate signing certificates ( public keys ) and verify signature correctness .
2021-05-03 13:37:47 +02:00
* /
2021-08-18 13:19:43 +02:00
public final class CertificateValidator {
2021-08-15 15:24:19 +02:00
2021-08-18 13:19:43 +02:00
private CertificateValidator ( ) {
2021-08-15 15:24:19 +02:00
}
2021-04-26 13:38:12 +02:00
2021-08-23 13:29:57 +02:00
private static final Logger LOGGER = LoggerFactory . getLogger ( CertificateValidator . class ) ;
2021-04-26 13:38:12 +02:00
2021-05-03 13:37:47 +02:00
/ * *
* Check if the signing key was eligible to create the provided signature .
*
* That entails :
* - Check , if the primary key is being revoked via key - revocation signatures .
* - Check , if the keys user - ids are revoked or not bound .
* - Check , if the signing subkey is revoked or expired .
* - Check , if the signing key is not capable of signing
*
* @param signature signature
* @param signingKeyRing signing key ring
* @param policy validation policy
* @return true if the signing key was eligible to create the signature
* @throws SignatureValidationException in case of a validation constraint violation
* /
2021-08-18 13:19:43 +02:00
public static boolean validateCertificate ( PGPSignature signature , PGPPublicKeyRing signingKeyRing , Policy policy )
2021-05-03 13:37:47 +02:00
throws SignatureValidationException {
2021-04-26 13:38:12 +02:00
Map < PGPSignature , Exception > rejections = new ConcurrentHashMap < > ( ) ;
2021-07-31 22:24:39 +02:00
long keyId = SignatureUtils . determineIssuerKeyId ( signature ) ;
PGPPublicKey signingSubkey = signingKeyRing . getPublicKey ( keyId ) ;
2021-04-26 13:38:12 +02:00
if ( signingSubkey = = null ) {
2021-07-31 22:24:39 +02:00
throw new SignatureValidationException ( " Provided key ring does not contain a subkey with id " + Long . toHexString ( keyId ) ) ;
2021-04-26 13:38:12 +02:00
}
PGPPublicKey primaryKey = signingKeyRing . getPublicKey ( ) ;
2021-05-03 13:37:47 +02:00
// Key-Revocation Signatures
2021-04-26 13:38:12 +02:00
List < PGPSignature > directKeySignatures = new ArrayList < > ( ) ;
Iterator < PGPSignature > primaryKeyRevocationIterator = primaryKey . getSignaturesOfType ( SignatureType . KEY_REVOCATION . getCode ( ) ) ;
while ( primaryKeyRevocationIterator . hasNext ( ) ) {
PGPSignature revocation = primaryKeyRevocationIterator . next ( ) ;
2022-04-05 14:10:04 +02:00
if ( revocation . getKeyID ( ) ! = primaryKey . getKeyID ( ) ) {
// Revocation was not made by primary key, skip
// TODO: What about external revocation keys?
}
2021-04-26 13:38:12 +02:00
try {
2021-08-18 14:28:36 +02:00
if ( SignatureVerifier . verifyKeyRevocationSignature ( revocation , primaryKey , policy , signature . getCreationTime ( ) ) ) {
2021-04-26 13:38:12 +02:00
directKeySignatures . add ( revocation ) ;
}
} catch ( SignatureValidationException e ) {
rejections . put ( revocation , e ) ;
2021-08-23 13:29:57 +02:00
LOGGER . debug ( " Rejecting key revocation signature: {} " , e . getMessage ( ) , e ) ;
2021-04-26 13:38:12 +02:00
}
}
2021-05-03 13:37:47 +02:00
// Direct-Key Signatures
2021-04-26 13:38:12 +02:00
Iterator < PGPSignature > keySignatures = primaryKey . getSignaturesOfType ( SignatureType . DIRECT_KEY . getCode ( ) ) ;
while ( keySignatures . hasNext ( ) ) {
PGPSignature keySignature = keySignatures . next ( ) ;
2022-04-05 14:10:04 +02:00
if ( keySignature . getKeyID ( ) ! = primaryKey . getKeyID ( ) ) {
// Signature was not made by primary key, skip
continue ;
}
2021-04-26 13:38:12 +02:00
try {
2021-08-18 14:28:36 +02:00
if ( SignatureVerifier . verifyDirectKeySignature ( keySignature , primaryKey , policy , signature . getCreationTime ( ) ) ) {
2021-04-26 13:38:12 +02:00
directKeySignatures . add ( keySignature ) ;
}
} catch ( SignatureValidationException e ) {
rejections . put ( keySignature , e ) ;
2021-08-23 13:29:57 +02:00
LOGGER . debug ( " Rejecting key signature: {} " , e . getMessage ( ) , e ) ;
2021-04-26 13:38:12 +02:00
}
}
2021-04-30 10:23:12 +02:00
Collections . sort ( directKeySignatures , new SignatureValidityComparator ( SignatureCreationDateComparator . Order . NEW_TO_OLD ) ) ;
2021-05-03 13:37:47 +02:00
if ( ! directKeySignatures . isEmpty ( ) ) {
2021-04-26 13:38:12 +02:00
if ( directKeySignatures . get ( 0 ) . getSignatureType ( ) = = SignatureType . KEY_REVOCATION . getCode ( ) ) {
throw new SignatureValidationException ( " Primary key has been revoked. " ) ;
}
}
2021-05-03 13:37:47 +02:00
// User-ID signatures (certifications, revocations)
2021-04-26 13:38:12 +02:00
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 ( ) ;
2022-04-05 14:10:04 +02:00
if ( userIdSig . getKeyID ( ) ! = primaryKey . getKeyID ( ) ) {
// Sig was made by external key, skip
continue ;
}
2021-04-26 13:38:12 +02:00
try {
2021-08-18 14:28:36 +02:00
if ( SignatureVerifier . verifySignatureOverUserId ( userId , userIdSig , primaryKey , policy , signature . getCreationTime ( ) ) ) {
2021-04-26 13:38:12 +02:00
signaturesOnUserId . add ( userIdSig ) ;
}
} catch ( SignatureValidationException e ) {
rejections . put ( userIdSig , e ) ;
2021-08-23 13:29:57 +02:00
LOGGER . debug ( " Rejecting user-id signature: {} " , e . getMessage ( ) , e ) ;
2021-04-26 13:38:12 +02:00
}
}
2021-04-30 10:23:12 +02:00
Collections . sort ( signaturesOnUserId , new SignatureValidityComparator ( SignatureCreationDateComparator . Order . NEW_TO_OLD ) ) ;
2021-04-26 13:38:12 +02:00
userIdSignatures . put ( userId , signaturesOnUserId ) ;
}
2021-05-03 13:37:47 +02:00
boolean anyUserIdValid = false ;
2021-04-26 13:38:12 +02:00
for ( String userId : userIdSignatures . keySet ( ) ) {
if ( ! userIdSignatures . get ( userId ) . isEmpty ( ) ) {
PGPSignature current = userIdSignatures . get ( userId ) . get ( 0 ) ;
if ( current . getSignatureType ( ) = = SignatureType . CERTIFICATION_REVOCATION . getCode ( ) ) {
2021-08-23 13:29:57 +02:00
LOGGER . debug ( " User-ID '{}' is revoked. " , userId ) ;
2021-04-26 13:38:12 +02:00
} else {
2021-05-03 13:37:47 +02:00
anyUserIdValid = true ;
2021-04-26 13:38:12 +02:00
}
}
}
2021-05-03 13:37:47 +02:00
if ( ! anyUserIdValid ) {
throw new SignatureValidationException ( " No valid user-id found. " , rejections ) ;
2021-04-26 13:38:12 +02:00
}
2021-05-03 13:37:47 +02:00
// Specific signer user-id
SignerUserID signerUserID = SignatureSubpacketsUtil . getSignerUserID ( signature ) ;
2022-03-11 18:23:40 +01:00
if ( signerUserID ! = null & & policy . getSignerUserIdValidationLevel ( ) = = Policy . SignerUserIdValidationLevel . STRICT ) {
2022-03-09 21:05:00 +01:00
List < PGPSignature > signerUserIdSigs = userIdSignatures . get ( signerUserID . getID ( ) ) ;
if ( signerUserIdSigs = = null | | signerUserIdSigs . isEmpty ( ) ) {
throw new SignatureValidationException ( " Signature was allegedly made by user-id ' " + signerUserID . getID ( ) +
" ' but we have no valid signatures for that on the certificate. " ) ;
}
PGPSignature userIdSig = signerUserIdSigs . get ( 0 ) ;
2021-05-03 13:37:47 +02:00
if ( userIdSig . getSignatureType ( ) = = SignatureType . CERTIFICATION_REVOCATION . getCode ( ) ) {
throw new SignatureValidationException ( " Signature was made with user-id ' " + signerUserID . getID ( ) + " ' which is revoked. " ) ;
}
}
if ( signingSubkey = = primaryKey ) {
if ( ! directKeySignatures . isEmpty ( ) ) {
if ( KeyFlag . hasKeyFlag ( SignatureSubpacketsUtil . getKeyFlags ( directKeySignatures . get ( 0 ) ) . getFlags ( ) , KeyFlag . SIGN_DATA ) ) {
return true ;
}
}
} // Subkey Binding Signatures / Subkey Revocation Signatures
else {
2021-04-26 13:38:12 +02:00
List < PGPSignature > subkeySigs = new ArrayList < > ( ) ;
Iterator < PGPSignature > bindingRevocations = signingSubkey . getSignaturesOfType ( SignatureType . SUBKEY_REVOCATION . getCode ( ) ) ;
while ( bindingRevocations . hasNext ( ) ) {
PGPSignature revocation = bindingRevocations . next ( ) ;
2022-04-05 14:10:04 +02:00
if ( revocation . getKeyID ( ) ! = primaryKey . getKeyID ( ) ) {
// Subkey Revocation was not made by primary key, skip
continue ;
}
2021-04-26 13:38:12 +02:00
try {
2021-08-18 14:28:36 +02:00
if ( SignatureVerifier . verifySubkeyBindingRevocation ( revocation , primaryKey , signingSubkey , policy , signature . getCreationTime ( ) ) ) {
2021-04-26 13:38:12 +02:00
subkeySigs . add ( revocation ) ;
}
} catch ( SignatureValidationException e ) {
rejections . put ( revocation , e ) ;
2021-08-23 13:29:57 +02:00
LOGGER . debug ( " Rejecting subkey revocation signature: {} " , e . getMessage ( ) , e ) ;
2021-04-26 13:38:12 +02:00
}
}
Iterator < PGPSignature > bindingSigs = signingSubkey . getSignaturesOfType ( SignatureType . SUBKEY_BINDING . getCode ( ) ) ;
while ( bindingSigs . hasNext ( ) ) {
PGPSignature bindingSig = bindingSigs . next ( ) ;
try {
2021-08-18 14:28:36 +02:00
if ( SignatureVerifier . verifySubkeyBindingSignature ( bindingSig , primaryKey , signingSubkey , policy , signature . getCreationTime ( ) ) ) {
2021-04-26 13:38:12 +02:00
subkeySigs . add ( bindingSig ) ;
}
} catch ( SignatureValidationException e ) {
rejections . put ( bindingSig , e ) ;
2021-08-23 13:29:57 +02:00
LOGGER . debug ( " Rejecting subkey binding signature: {} " , e . getMessage ( ) , e ) ;
2021-04-26 13:38:12 +02:00
}
}
2021-04-30 10:23:12 +02:00
Collections . sort ( subkeySigs , new SignatureValidityComparator ( SignatureCreationDateComparator . Order . NEW_TO_OLD ) ) ;
2021-04-26 13:38:12 +02:00
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. " ) ;
}
2021-05-25 16:25:22 +02:00
KeyFlags keyFlags = SignatureSubpacketsUtil . getKeyFlags ( currentSig ) ;
if ( keyFlags = = null ) {
if ( directKeySignatures . isEmpty ( ) ) {
throw new SignatureValidationException ( " Signature was made by key which is not capable of signing (no keyflags on binding sig, no direct-key sig). " ) ;
}
PGPSignature directKeySig = directKeySignatures . get ( 0 ) ;
KeyFlags directKeyFlags = SignatureSubpacketsUtil . getKeyFlags ( directKeySig ) ;
if ( ! KeyFlag . hasKeyFlag ( directKeyFlags . getFlags ( ) , KeyFlag . SIGN_DATA ) ) {
throw new SignatureValidationException ( " Signature was made by key which is not capable of signing (no keyflags on binding sig, no SIGN flag on direct-key sig). " ) ;
}
} else if ( ! KeyFlag . hasKeyFlag ( keyFlags . getFlags ( ) , KeyFlag . SIGN_DATA ) ) {
throw new SignatureValidationException ( " Signature was made by key which is not capable of signing (no SIGN flag on binding sig). " ) ;
2021-04-26 13:38:12 +02:00
}
}
return true ;
}
2021-05-03 13:37:47 +02:00
/ * *
* Validate the given signing key and then verify the given signature while parsing out the signed data .
* Uninitialized means that no signed data has been read and the hash generators state has not yet been updated .
*
* @param signature uninitialized signature
* @param signedData input stream containing signed data
* @param signingKeyRing key ring containing signing key
* @param policy validation policy
* @param validationDate date of validation
* @return true if the signature is valid , false otherwise
* @throws SignatureValidationException for validation constraint violations
* /
2021-08-18 13:19:43 +02:00
public static boolean validateCertificateAndVerifyUninitializedSignature ( PGPSignature signature ,
InputStream signedData ,
PGPPublicKeyRing signingKeyRing ,
Policy policy ,
Date validationDate )
2021-04-26 13:38:12 +02:00
throws SignatureValidationException {
2021-08-18 13:19:43 +02:00
validateCertificate ( signature , signingKeyRing , policy ) ;
2021-07-31 22:24:39 +02:00
long keyId = SignatureUtils . determineIssuerKeyId ( signature ) ;
2021-08-18 14:28:36 +02:00
return SignatureVerifier . verifyUninitializedSignature ( signature , signedData , signingKeyRing . getPublicKey ( keyId ) , policy , validationDate ) ;
2021-04-26 13:38:12 +02:00
}
2021-05-03 13:37:47 +02:00
/ * *
* Validate the signing key and the given initialized signature .
* Initialized means that the signatures hash generator has already been updated by reading the signed data completely .
*
* @param signature initialized signature
* @param verificationKeys key ring containing the verification key
* @param policy validation policy
* @return true if the signature is valid , false otherwise
* @throws SignatureValidationException in case of a validation constraint violation
* /
2021-08-18 13:19:43 +02:00
public static boolean validateCertificateAndVerifyInitializedSignature ( PGPSignature signature , PGPPublicKeyRing verificationKeys , Policy policy )
2021-05-03 13:37:47 +02:00
throws SignatureValidationException {
2021-08-18 13:19:43 +02:00
validateCertificate ( signature , verificationKeys , policy ) ;
2021-07-31 22:24:39 +02:00
long keyId = SignatureUtils . determineIssuerKeyId ( signature ) ;
PGPPublicKey signingKey = verificationKeys . getPublicKey ( keyId ) ;
2021-08-18 14:28:36 +02:00
SignatureVerifier . verifyInitializedSignature ( signature , signingKey , policy , signature . getCreationTime ( ) ) ;
2021-04-26 13:38:12 +02:00
return true ;
}
2021-08-18 12:55:24 +02:00
2021-08-18 14:28:36 +02:00
/ * *
2021-09-02 18:01:06 +02:00
* Validate the signing key certificate and the given { @link OnePassSignatureCheck } .
2021-08-18 14:28:36 +02:00
*
* @param onePassSignature corresponding one - pass - signature
* @param policy policy
* @return true if the certificate is valid and the signature is correct , false otherwise .
* @throws SignatureValidationException in case of a validation error
* /
2021-09-02 18:01:06 +02:00
public static boolean validateCertificateAndVerifyOnePassSignature ( OnePassSignatureCheck onePassSignature , Policy policy )
2021-08-18 13:19:43 +02:00
throws SignatureValidationException {
2021-09-02 18:01:06 +02:00
PGPSignature signature = onePassSignature . getSignature ( ) ;
2021-08-18 13:19:43 +02:00
validateCertificate ( signature , onePassSignature . getVerificationKeys ( ) , policy ) ;
2021-08-18 12:55:24 +02:00
PGPPublicKey signingKey = onePassSignature . getVerificationKeys ( ) . getPublicKey ( signature . getKeyID ( ) ) ;
2021-08-18 13:19:43 +02:00
verifyOnePassSignature ( signature , signingKey , onePassSignature , policy ) ;
2021-08-18 12:55:24 +02:00
return true ;
}
2021-04-26 13:38:12 +02:00
}