1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2025-09-09 18:29:39 +02:00

Test v6 third party certification generation

This commit is contained in:
Paul Schaub 2025-03-26 13:20:17 +01:00
parent 4a7e690806
commit a8cbd36a52
Signed by: vanitasvitae
GPG key ID: 62BEE9264BF17311
3 changed files with 226 additions and 0 deletions

View file

@ -15,6 +15,7 @@ import org.bouncycastle.openpgp.api.OpenPGPSignature
import org.pgpainless.PGPainless
import org.pgpainless.algorithm.CertificationType
import org.pgpainless.algorithm.KeyFlag
import org.pgpainless.algorithm.SignatureType
import org.pgpainless.algorithm.Trustworthiness
import org.pgpainless.exception.KeyException
import org.pgpainless.exception.KeyException.ExpiredKeyException
@ -22,9 +23,11 @@ import org.pgpainless.exception.KeyException.MissingSecretKeyException
import org.pgpainless.exception.KeyException.RevokedKeyException
import org.pgpainless.key.protection.SecretKeyRingProtector
import org.pgpainless.key.util.KeyRingUtils
import org.pgpainless.signature.builder.RevocationSignatureBuilder
import org.pgpainless.signature.builder.ThirdPartyCertificationSignatureBuilder
import org.pgpainless.signature.builder.ThirdPartyDirectKeySignatureBuilder
import org.pgpainless.signature.subpackets.CertificationSubpackets
import org.pgpainless.signature.subpackets.RevocationSignatureSubpackets
/**
* API for creating certifications and delegations (Signatures) on keys. This API can be used to
@ -78,6 +81,16 @@ class CertifyCertificate(private val api: PGPainless) {
certificationType: CertificationType
) = CertificationOnUserId(userId, certificate, certificationType, api)
/**
* Create a certification revocation signature for the given [userId] on the given
* [certificate].
*
* @param userId userid to revoke
* @param certificate certificate carrying the userid
*/
fun revokeUserIdOnCertificate(userId: CharSequence, certificate: OpenPGPCertificate) =
RevocationOnUserId(userId, certificate, api)
/**
* Create a delegation (direct key signature) over a certificate. This can be used to mark a
* certificate as a trusted introducer (see [certificate] method with [Trustworthiness]
@ -115,6 +128,14 @@ class CertifyCertificate(private val api: PGPainless) {
fun certificate(certificate: PGPPublicKeyRing, trustworthiness: Trustworthiness?) =
DelegationOnCertificate(certificate, trustworthiness, api)
/**
* Create a key revocation signature, revoking a delegation over the given [certificate].
*
* @param certificate certificate to revoke the delegation to
*/
fun revokeCertificate(certificate: OpenPGPCertificate): RevocationOnCertificate =
RevocationOnCertificate(certificate, api)
class CertificationOnUserId(
private val userId: CharSequence,
private val certificate: OpenPGPCertificate,
@ -203,6 +224,63 @@ class CertifyCertificate(private val api: PGPainless) {
}
}
class RevocationOnUserId(
private val userId: CharSequence,
private val certificate: OpenPGPCertificate,
private val api: PGPainless
) {
fun withKey(
key: OpenPGPKey,
protector: SecretKeyRingProtector
): RevocationOnUserIdWithSubpackets {
val secretKey = getCertifyingSecretKey(key, api)
val sigBuilder =
RevocationSignatureBuilder(
SignatureType.CERTIFICATION_REVOCATION, secretKey, protector, api)
return RevocationOnUserIdWithSubpackets(certificate, userId, sigBuilder, api)
}
}
class RevocationOnUserIdWithSubpackets(
private val certificate: OpenPGPCertificate,
private val userId: CharSequence,
private val sigBuilder: RevocationSignatureBuilder,
private val api: PGPainless
) {
/**
* Apply the given signature subpackets and build the revocation signature.
*
* @param subpacketCallback callback to modify the revocation signatures subpackets
* @return result
* @throws PGPException in case of an OpenPGP related error
*/
fun buildWithSubpackets(
subpacketCallback: RevocationSignatureSubpackets.Callback
): CertificationResult {
sigBuilder.applyCallback(subpacketCallback)
return build()
}
/**
* Build the revocation signature.
*
* @return result
* @throws PGPException in case of an OpenPGP related error
*/
fun build(): CertificationResult {
val signature = sigBuilder.build(certificate.primaryKey, userId)
val certifiedCertificate =
api.toCertificate(
KeyRingUtils.injectCertification(
certificate.pgpPublicKeyRing, userId, signature.signature))
return CertificationResult(certifiedCertificate, signature)
}
}
class DelegationOnCertificate(
private val certificate: OpenPGPCertificate,
private val trustworthiness: Trustworthiness?,
@ -290,6 +368,61 @@ class CertifyCertificate(private val api: PGPainless) {
}
}
class RevocationOnCertificate(
private val certificate: OpenPGPCertificate,
private val api: PGPainless
) {
fun withKey(
key: OpenPGPKey,
protector: SecretKeyRingProtector
): RevocationOnCertificateWithSubpackets {
val secretKey = getCertifyingSecretKey(key, api)
val sigBuilder =
RevocationSignatureBuilder(SignatureType.KEY_REVOCATION, secretKey, protector, api)
return RevocationOnCertificateWithSubpackets(certificate, sigBuilder, api)
}
}
class RevocationOnCertificateWithSubpackets(
private val certificate: OpenPGPCertificate,
private val sigBuilder: RevocationSignatureBuilder,
private val api: PGPainless
) {
/**
* Apply the given signature subpackets and build the delegation revocation signature.
*
* @param subpacketsCallback callback to modify the revocations subpackets
* @return result
* @throws PGPException in case of an OpenPGP related error
*/
fun buildWithSubpackets(
subpacketsCallback: RevocationSignatureSubpackets.Callback
): CertificationResult {
sigBuilder.applyCallback(subpacketsCallback)
return build()
}
/**
* Build the delegation revocation signature.
*
* @return result
* @throws PGPException in case of an OpenPGP related error
*/
fun build(): CertificationResult {
val revokedKey = certificate.primaryKey
val revocation = sigBuilder.build(revokedKey)
val revokedCertificate =
api.toCertificate(
KeyRingUtils.injectCertification(
certificate.pgpPublicKeyRing,
revokedKey.pgpPublicKey,
revocation.signature))
return CertificationResult(revokedCertificate, revocation)
}
}
/**
* Result of a certification operation.
*

View file

@ -83,6 +83,21 @@ constructor(
build(revokeeUserId.userId), signingKey.publicKey, revokeeUserId)
}
fun build(
revokeeKey: OpenPGPComponentKey,
revokeeUserId: CharSequence
): OpenPGPComponentSignature =
OpenPGPComponentSignature(
buildAndInitSignatureGenerator()
.also {
require(_signatureType == SignatureType.CERTIFICATION_REVOCATION) {
"Signature type is != CERTIFICATION_REVOCATION."
}
}
.generateCertification(revokeeUserId.toString(), revokeeKey.pgpPublicKey),
signingKey.publicKey,
revokeeKey.certificate.getUserId(revokeeUserId.toString()))
init {
hashedSubpackets.setRevocable(false)
}

View file

@ -0,0 +1,78 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.key.certification;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.api.OpenPGPCertificate;
import org.bouncycastle.openpgp.api.OpenPGPKey;
import org.junit.jupiter.api.Test;
import org.pgpainless.PGPainless;
import org.pgpainless.algorithm.OpenPGPKeyVersion;
import org.pgpainless.key.protection.SecretKeyRingProtector;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class CertifyV6CertificateTest {
@Test
public void testCertifyV6CertWithV6Key() throws PGPException {
PGPainless api = PGPainless.getInstance();
OpenPGPKey aliceKey = api.generateKey(OpenPGPKeyVersion.v6)
.modernKeyRing("Alice <alice@pgpainless.org>");
OpenPGPKey bobKey = api.generateKey(OpenPGPKeyVersion.v6)
.modernKeyRing("Bob <bob@pgpainless.org>");
OpenPGPCertificate bobCert = bobKey.toCertificate();
// Create a certification on Bobs certificate
OpenPGPCertificate bobCertified = api.generateCertification()
.userIdOnCertificate("Bob <bob@pgpainless.org>", bobCert)
.withKey(aliceKey, SecretKeyRingProtector.unprotectedKeys())
.build().getCertifiedCertificate();
// Check that there is a valid certification chain from Alice to Bobs UID
OpenPGPCertificate.OpenPGPSignatureChain signatureChain =
bobCertified.getUserId("Bob <bob@pgpainless.org>")
.getCertificationBy(aliceKey.toCertificate());
assertNotNull(signatureChain);
assertTrue(signatureChain.isValid());
// Revoke Alice' key and...
OpenPGPKey aliceRevoked = api.modify(aliceKey)
.revoke(SecretKeyRingProtector.unprotectedKeys())
.done();
// ...verify we no longer have a valid certification chain
OpenPGPCertificate.OpenPGPSignatureChain missingChain =
bobCertified.getUserId("Bob <bob@pgpainless.org>")
.getCertificationBy(aliceRevoked.toCertificate());
assertNull(missingChain);
OpenPGPCertificate.OpenPGPSignatureChain revokedChain =
bobCertified.getUserId("Bob <bob@pgpainless.org>")
.getRevocationBy(aliceRevoked);
assertNotNull(revokedChain);
assertTrue(revokedChain.isValid());
// Instead, revoke the certification itself and...
bobCertified = api.generateCertification()
.revokeUserIdOnCertificate("Bob <bob@pgpainless.org>", bobCertified)
.withKey(aliceKey, SecretKeyRingProtector.unprotectedKeys())
.build().getCertifiedCertificate();
// ...verify we now have a valid certification chain
OpenPGPCertificate.OpenPGPSignatureChain brokenChain =
bobCertified.getUserId("Bob <bob@pgpainless.org>")
.getRevocationBy(aliceKey.toCertificate());
assertNotNull(brokenChain);
assertTrue(brokenChain.isValid());
}
}