1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2025-12-12 23:31:08 +01:00

Kotlin conversion: SignatureVerifier

This commit is contained in:
Paul Schaub 2023-10-26 12:52:04 +02:00
parent dc05a492f5
commit 19b45644ae
Signed by: vanitasvitae
GPG key ID: 62BEE9264BF17311
4 changed files with 596 additions and 494 deletions

View file

@ -0,0 +1,36 @@
<!--
SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
SPDX-License-Identifier: Apache-2.0
-->
# Signature Verification and Validation
This package can be a bit overwhelming, hence this README file.
Signature verification and validation in OpenPGP is a complex topic (see a
[related blog post of mine](https://blog.jabberhead.tk/2021/04/03/why-signature-verification-in-openpgp-is-hard/)),
therefore let me quickly outline some of its challenges for you:
A signature is either valid or it is not.
However, signature validity goes beyond merely checking the cryptographic correctness like BouncyCastle does.
A signature that is correct can still be invalid, e.g. if it is past its expiry date
or the key that issued the signature got revoked or is simply not a signing key in the first place.
All the little criteria like "is not expired", "has a hashed signature creation time subpacket",
"does not contain critical unknown notations/subpackets" and so forth
are implemented in the SignatureValidator class. This class defines an abstract "verify()" method
which is overwritten in a collection of anonymous subclasses which check for one or more such criteria.
Whether a signature is cryptographically correct is checked in the SignatureVerifier class.
This class draws on the SignatureValidator class to compose the subclass building blocks depending on
the signature type to check if the signature fulfills formal criteria and further checks for
cryptographic correctness.
Lastly the CertificateValidator class not only verifies single signatures, but also verifies that
the corresponding certificate (public key ring) is still valid.
It checks if the signing subkey is properly bound to its primary key, that no key in the chain is
revoked or expired and that the signing key is capable of signing in the first place.
I hope this little guide helps you to get access to the package more quickly.
Happy Hacking!

View file

@ -0,0 +1,596 @@
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.signature.consumer
import java.io.IOException
import java.io.InputStream
import java.util.*
import openpgp.openPgpKeyId
import org.bouncycastle.openpgp.PGPException
import org.bouncycastle.openpgp.PGPPublicKey
import org.bouncycastle.openpgp.PGPSignature
import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVector
import org.pgpainless.algorithm.SignatureType
import org.pgpainless.exception.SignatureValidationException
import org.pgpainless.implementation.ImplementationFactory.Companion.getInstance
import org.pgpainless.policy.Policy
import org.pgpainless.signature.consumer.SignatureValidator.Companion.correctSignatureOverKey
import org.pgpainless.signature.consumer.SignatureValidator.Companion.correctSignatureOverUserAttributes
import org.pgpainless.signature.consumer.SignatureValidator.Companion.correctSignatureOverUserId
import org.pgpainless.signature.consumer.SignatureValidator.Companion.correctSubkeyBindingSignature
import org.pgpainless.signature.consumer.SignatureValidator.Companion.hasValidPrimaryKeyBindingSignatureIfRequired
import org.pgpainless.signature.consumer.SignatureValidator.Companion.signatureDoesNotPredateSignee
import org.pgpainless.signature.consumer.SignatureValidator.Companion.signatureIsCertification
import org.pgpainless.signature.consumer.SignatureValidator.Companion.signatureIsEffective
import org.pgpainless.signature.consumer.SignatureValidator.Companion.signatureIsOfType
import org.pgpainless.signature.consumer.SignatureValidator.Companion.signatureStructureIsAcceptable
import org.pgpainless.signature.consumer.SignatureValidator.Companion.wasPossiblyMadeByKey
/**
* Collection of static methods for signature verification. Signature verification entails
* validation of certain criteria (see [SignatureValidator]), as well as cryptographic verification
* of signature correctness.
*/
class SignatureVerifier {
companion object {
/**
* Verify a signature (certification or revocation) over a user-id.
*
* @param userId user-id
* @param signature certification signature
* @param signingKey key that created the certification
* @param keyWithUserId key carrying the user-id
* @param policy policy
* @param referenceTime reference date for signature verification
* @return true if signature verification is successful
* @throws SignatureValidationException if signature verification fails for some reason
*/
@JvmStatic
@Throws(SignatureValidationException::class)
fun verifySignatureOverUserId(
userId: CharSequence,
signature: PGPSignature,
signingKey: PGPPublicKey,
keyWithUserId: PGPPublicKey,
policy: Policy,
referenceTime: Date
): Boolean {
val type = SignatureType.requireFromCode(signature.signatureType)
return when (type) {
SignatureType.GENERIC_CERTIFICATION,
SignatureType.NO_CERTIFICATION,
SignatureType.CASUAL_CERTIFICATION,
SignatureType.POSITIVE_CERTIFICATION ->
verifyUserIdCertification(
userId, signature, signingKey, keyWithUserId, policy, referenceTime)
SignatureType.CERTIFICATION_REVOCATION ->
verifyUserIdRevocation(
userId, signature, signingKey, keyWithUserId, policy, referenceTime)
else ->
throw SignatureValidationException(
"Signature is not a valid user-id certification/revocation signature: $type")
}
}
/**
* Verify a certification self-signature over a user-id.
*
* @param userId user-id
* @param signature certification signature
* @param primaryKey primary key
* @param policy policy
* @param referenceTime reference date for signature verification
* @return true if the self-signature is verified successfully
* @throws SignatureValidationException if signature verification fails for some reason
*/
@JvmStatic
@Throws(SignatureValidationException::class)
fun verifyUserIdCertification(
userId: CharSequence,
signature: PGPSignature,
primaryKey: PGPPublicKey,
policy: Policy,
referenceTime: Date
): Boolean {
return verifyUserIdCertification(
userId, signature, primaryKey, primaryKey, policy, referenceTime)
}
/**
* Verify a user-id certification.
*
* @param userId user-id
* @param signature certification signature
* @param signingKey key that created the certification
* @param keyWithUserId primary key that carries the user-id
* @param policy policy
* @param referenceTime reference date for signature verification
* @return true if signature verification is successful
* @throws SignatureValidationException if signature verification fails for some reason
*/
@JvmStatic
@Throws(SignatureValidationException::class)
fun verifyUserIdCertification(
userId: CharSequence,
signature: PGPSignature,
signingKey: PGPPublicKey,
keyWithUserId: PGPPublicKey,
policy: Policy,
referenceTime: Date
): Boolean {
wasPossiblyMadeByKey(signingKey).verify(signature)
signatureIsCertification().verify(signature)
signatureStructureIsAcceptable(signingKey, policy).verify(signature)
signatureIsEffective(referenceTime).verify(signature)
correctSignatureOverUserId(userId, keyWithUserId, signingKey).verify(signature)
return true
}
/**
* Verify a user-id revocation self-signature.
*
* @param userId user-id
* @param signature user-id revocation signature
* @param primaryKey primary key
* @param policy policy
* @param referenceTime reference date for signature verification
* @return true if the user-id revocation signature is successfully verified
* @throws SignatureValidationException if signature verification fails for some reason
*/
@JvmStatic
@Throws(SignatureValidationException::class)
fun verifyUserIdRevocation(
userId: CharSequence,
signature: PGPSignature,
primaryKey: PGPPublicKey,
policy: Policy,
referenceTime: Date
): Boolean {
return verifyUserIdRevocation(
userId, signature, primaryKey, primaryKey, policy, referenceTime)
}
/**
* Verify a user-id revocation signature.
*
* @param userId user-id
* @param signature revocation signature
* @param signingKey key that created the revocation signature
* @param keyWithUserId primary key carrying the user-id
* @param policy policy
* @param referenceTime reference date for signature verification
* @return true if the user-id revocation signature is successfully verified
* @throws SignatureValidationException if signature verification fails for some reason
*/
@JvmStatic
@Throws(SignatureValidationException::class)
fun verifyUserIdRevocation(
userId: CharSequence,
signature: PGPSignature,
signingKey: PGPPublicKey,
keyWithUserId: PGPPublicKey,
policy: Policy,
referenceTime: Date
): Boolean {
wasPossiblyMadeByKey(signingKey).verify(signature)
signatureIsOfType(SignatureType.CERTIFICATION_REVOCATION).verify(signature)
signatureStructureIsAcceptable(signingKey, policy).verify(signature)
signatureIsEffective(referenceTime).verify(signature)
correctSignatureOverUserId(userId, keyWithUserId, signingKey).verify(signature)
return true
}
/**
* Verify a certification self-signature over a user-attributes packet.
*
* @param userAttributes user attributes
* @param signature certification self-signature
* @param primaryKey primary key that carries the user-attributes
* @param policy policy
* @param referenceTime reference date for signature verification
* @return true if the signature can be verified successfully
* @throws SignatureValidationException if signature verification fails for some reason
*/
@JvmStatic
@Throws(SignatureValidationException::class)
fun verifyUserAttributesCertification(
userAttributes: PGPUserAttributeSubpacketVector,
signature: PGPSignature,
primaryKey: PGPPublicKey,
policy: Policy,
referenceTime: Date
): Boolean {
return verifyUserAttributesCertification(
userAttributes, signature, primaryKey, primaryKey, policy, referenceTime)
}
/**
* Verify a certification signature over a user-attributes packet.
*
* @param userAttributes user attributes
* @param signature certification signature
* @param signingKey key that created the user-attributes certification
* @param keyWithAttributes key that carries the user-attributes certification
* @param policy policy
* @param referenceTime reference date for signature verification
* @return true if the signature can be verified successfully
* @throws SignatureValidationException if signature verification fails for some reason
*/
@JvmStatic
@Throws(SignatureValidationException::class)
fun verifyUserAttributesCertification(
userAttributes: PGPUserAttributeSubpacketVector,
signature: PGPSignature,
signingKey: PGPPublicKey,
keyWithAttributes: PGPPublicKey,
policy: Policy,
referenceTime: Date
): Boolean {
wasPossiblyMadeByKey(signingKey).verify(signature)
signatureIsCertification().verify(signature)
signatureStructureIsAcceptable(signingKey, policy).verify(signature)
signatureIsEffective(referenceTime).verify(signature)
correctSignatureOverUserAttributes(userAttributes, keyWithAttributes, signingKey)
.verify(signature)
return true
}
/**
* Verify a user-attributes revocation self-signature.
*
* @param userAttributes user-attributes
* @param signature user-attributes revocation signature
* @param primaryKey primary key that carries the user-attributes
* @param policy policy
* @param referenceTime reference date for signature verification
* @return true if the revocation signature can be verified successfully
* @throws SignatureValidationException if signature verification fails for some reason
*/
@JvmStatic
@Throws(SignatureValidationException::class)
fun verifyUserAttributesRevocation(
userAttributes: PGPUserAttributeSubpacketVector,
signature: PGPSignature,
primaryKey: PGPPublicKey,
policy: Policy,
referenceTime: Date
): Boolean {
return verifyUserAttributesRevocation(
userAttributes, signature, primaryKey, primaryKey, policy, referenceTime)
}
/**
* Verify a user-attributes revocation signature.
*
* @param userAttributes user-attributes
* @param signature revocation signature
* @param signingKey revocation key
* @param keyWithAttributes key that carries the user-attributes
* @param policy policy
* @param referenceTime reference date for signature verification
* @return true if the revocation signature can be verified successfully
* @throws SignatureValidationException if signature verification fails for some reason
*/
@JvmStatic
@Throws(SignatureValidationException::class)
fun verifyUserAttributesRevocation(
userAttributes: PGPUserAttributeSubpacketVector,
signature: PGPSignature,
signingKey: PGPPublicKey,
keyWithAttributes: PGPPublicKey,
policy: Policy,
referenceTime: Date
): Boolean {
wasPossiblyMadeByKey(signingKey).verify(signature)
signatureIsOfType(SignatureType.CERTIFICATION_REVOCATION).verify(signature)
signatureStructureIsAcceptable(signingKey, policy).verify(signature)
signatureIsEffective(referenceTime).verify(signature)
correctSignatureOverUserAttributes(userAttributes, keyWithAttributes, signingKey)
.verify(signature)
return true
}
/**
* Verify a subkey binding signature.
*
* @param signature binding signature
* @param primaryKey primary key
* @param subkey subkey
* @param policy policy
* @param referenceTime reference date for signature verification
* @return true if the binding signature can be verified successfully
* @throws SignatureValidationException if signature verification fails for some reason
*/
@JvmStatic
@Throws(SignatureValidationException::class)
fun verifySubkeyBindingSignature(
signature: PGPSignature,
primaryKey: PGPPublicKey,
subkey: PGPPublicKey,
policy: Policy,
referenceTime: Date
): Boolean {
signatureIsOfType(SignatureType.SUBKEY_BINDING).verify(signature)
signatureStructureIsAcceptable(primaryKey, policy).verify(signature)
signatureDoesNotPredateSignee(subkey).verify(signature)
signatureIsEffective(referenceTime).verify(signature)
hasValidPrimaryKeyBindingSignatureIfRequired(primaryKey, subkey, policy, referenceTime)
.verify(signature)
correctSubkeyBindingSignature(primaryKey, subkey).verify(signature)
return true
}
/**
* Verify a subkey revocation signature.
*
* @param signature subkey revocation signature
* @param primaryKey primary key
* @param subkey subkey
* @param policy policy
* @param referenceTime reference date for signature verification
* @return true if the subkey revocation signature can be verified successfully
* @throws SignatureValidationException if signature verification fails for some reason
*/
@JvmStatic
@Throws(SignatureValidationException::class)
fun verifySubkeyBindingRevocation(
signature: PGPSignature,
primaryKey: PGPPublicKey,
subkey: PGPPublicKey,
policy: Policy,
referenceTime: Date
): Boolean {
signatureIsOfType(SignatureType.SUBKEY_REVOCATION).verify(signature)
signatureStructureIsAcceptable(primaryKey, policy).verify(signature)
signatureDoesNotPredateSignee(subkey).verify(signature)
signatureIsEffective(referenceTime).verify(signature)
correctSignatureOverKey(primaryKey, subkey).verify(signature)
return true
}
/**
* Verify a direct-key self-signature.
*
* @param signature signature
* @param primaryKey primary key
* @param policy policy
* @param referenceTime reference date for signature verification
* @return true if the signature can be verified successfully
* @throws SignatureValidationException if signature verification fails for some reason
*/
@JvmStatic
@Throws(SignatureValidationException::class)
fun verifyDirectKeySignature(
signature: PGPSignature,
primaryKey: PGPPublicKey,
policy: Policy,
referenceTime: Date
): Boolean {
return verifyDirectKeySignature(
signature, primaryKey, primaryKey, policy, referenceTime)
}
/**
* Verify a direct-key signature.
*
* @param signature signature
* @param signingKey signing key
* @param signedKey signed key
* @param policy policy
* @param referenceTime reference date for signature verification
* @return true if signature verification is successful
* @throws SignatureValidationException if signature verification fails for some reason
*/
@JvmStatic
@Throws(SignatureValidationException::class)
fun verifyDirectKeySignature(
signature: PGPSignature,
signingKey: PGPPublicKey,
signedKey: PGPPublicKey,
policy: Policy,
referenceTime: Date
): Boolean {
signatureIsOfType(SignatureType.DIRECT_KEY).verify(signature)
signatureStructureIsAcceptable(signingKey, policy).verify(signature)
signatureDoesNotPredateSignee(signedKey).verify(signature)
signatureIsEffective(referenceTime).verify(signature)
correctSignatureOverKey(signingKey, signedKey).verify(signature)
return true
}
/**
* Verify a key revocation signature.
*
* @param signature signature
* @param primaryKey primary key
* @param policy policy
* @param referenceTime reference date for signature verification
* @return true if signature verification is successful
* @throws SignatureValidationException if signature verification fails for some reason
*/
@JvmStatic
@Throws(SignatureValidationException::class)
fun verifyKeyRevocationSignature(
signature: PGPSignature,
primaryKey: PGPPublicKey,
policy: Policy,
referenceTime: Date
): Boolean {
signatureIsOfType(SignatureType.KEY_REVOCATION).verify(signature)
signatureStructureIsAcceptable(primaryKey, policy).verify(signature)
signatureIsEffective(referenceTime).verify(signature)
correctSignatureOverKey(primaryKey, primaryKey).verify(signature)
return true
}
/**
* Initialize a signature and verify it afterwards by updating it with the signed data.
*
* @param signature OpenPGP signature
* @param signedData input stream containing the signed data
* @param signingKey the key that created the signature
* @param policy policy
* @param referenceTime reference date of signature verification
* @return true if the signature is successfully verified
* @throws SignatureValidationException if the signature verification fails for some reason
*/
@JvmStatic
@Throws(SignatureValidationException::class)
fun verifyUninitializedSignature(
signature: PGPSignature,
signedData: InputStream,
signingKey: PGPPublicKey,
policy: Policy,
referenceTime: Date
): Boolean {
initializeSignatureAndUpdateWithSignedData(signature, signedData, signingKey)
return verifyInitializedSignature(signature, signingKey, policy, referenceTime)
}
/**
* Initialize a signature and then update it with the signed data from the given
* [InputStream].
*
* @param signature OpenPGP signature
* @param signedData input stream containing signed data
* @param signingKey key that created the signature
* @throws SignatureValidationException in case the signature cannot be verified for some
* reason
*/
@JvmStatic
@Throws(SignatureValidationException::class)
fun initializeSignatureAndUpdateWithSignedData(
signature: PGPSignature,
signedData: InputStream,
signingKey: PGPPublicKey
) {
try {
signature.init(
getInstance().pgpContentVerifierBuilderProvider,
signingKey,
)
var read: Int
val buf = ByteArray(8192)
var lastByte: Byte = -1
while (signedData.read(buf).also { read = it } != -1) {
// If we previously omitted a newline, but the stream is not yet empty, add it
// now
if (lastByte == '\n'.code.toByte()) {
signature.update(lastByte)
}
lastByte = buf[read - 1]
if (lastByte == '\n'.code.toByte()) {
// if last byte in buffer is newline, omit it for now
signature.update(buf, 0, read - 1)
} else {
// otherwise, write buffer as usual
signature.update(buf, 0, read)
}
}
} catch (e: PGPException) {
throw SignatureValidationException("Cannot init signature.", e)
} catch (e: IOException) {
throw SignatureValidationException("Cannot update signature.", e)
}
}
/**
* Verify an initialized signature. An initialized signature was already updated with the
* signed data.
*
* @param signature OpenPGP signature
* @param signingKey key that created the signature
* @param policy policy
* @param referenceTime reference date for signature verification
* @return true if signature is verified successfully
* @throws SignatureValidationException if signature verification fails for some reason
*/
@JvmStatic
@Throws(SignatureValidationException::class)
fun verifyInitializedSignature(
signature: PGPSignature,
signingKey: PGPPublicKey,
policy: Policy,
referenceTime: Date
): Boolean {
wasPossiblyMadeByKey(signingKey).verify(signature)
signatureStructureIsAcceptable(signingKey, policy).verify(signature)
signatureIsEffective(referenceTime).verify(signature)
return try {
if (!signature.verify()) {
throw SignatureValidationException("Signature is not correct.")
}
true
} catch (e: PGPException) {
throw SignatureValidationException("Could not verify signature correctness.", e)
}
}
@JvmStatic
@Throws(SignatureValidationException::class)
fun verifyOnePassSignature(
signature: PGPSignature,
signingKey: PGPPublicKey,
onePassSignature: OnePassSignatureCheck,
policy: Policy
): Boolean {
try {
wasPossiblyMadeByKey(signingKey).verify(signature)
signatureStructureIsAcceptable(signingKey, policy).verify(signature)
signatureIsEffective().verify(signature)
} catch (e: SignatureValidationException) {
throw SignatureValidationException("Signature is not valid: ${e.message}", e)
}
try {
checkNotNull(onePassSignature.signature) { "No comparison signature provided." }
if (!onePassSignature.onePassSignature.verify(signature)) {
throw SignatureValidationException(
"Bad signature of key ${signingKey.keyID.openPgpKeyId()}")
}
} catch (e: PGPException) {
throw SignatureValidationException(
"Could not verify correctness of One-Pass-Signature: ${e.message}", e)
}
return true
}
/**
* Verify a signature (certification or revocation) over a user-id.
*
* @param userId user-id
* @param signature self-signature
* @param primaryKey primary key that created the signature
* @param policy policy
* @param referenceTime reference date for signature verification
* @return true if the signature is successfully verified
* @throws SignatureValidationException if signature verification fails for some reason
*/
@JvmStatic
@Throws(SignatureValidationException::class)
fun verifySignatureOverUserId(
userId: CharSequence,
signature: PGPSignature,
primaryKey: PGPPublicKey,
policy: Policy,
referenceTime: Date
): Boolean {
return verifySignatureOverUserId(
userId, signature, primaryKey, primaryKey, policy, referenceTime)
}
}
}