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:
parent
4a7e690806
commit
a8cbd36a52
3 changed files with 226 additions and 0 deletions
|
@ -15,6 +15,7 @@ import org.bouncycastle.openpgp.api.OpenPGPSignature
|
||||||
import org.pgpainless.PGPainless
|
import org.pgpainless.PGPainless
|
||||||
import org.pgpainless.algorithm.CertificationType
|
import org.pgpainless.algorithm.CertificationType
|
||||||
import org.pgpainless.algorithm.KeyFlag
|
import org.pgpainless.algorithm.KeyFlag
|
||||||
|
import org.pgpainless.algorithm.SignatureType
|
||||||
import org.pgpainless.algorithm.Trustworthiness
|
import org.pgpainless.algorithm.Trustworthiness
|
||||||
import org.pgpainless.exception.KeyException
|
import org.pgpainless.exception.KeyException
|
||||||
import org.pgpainless.exception.KeyException.ExpiredKeyException
|
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.exception.KeyException.RevokedKeyException
|
||||||
import org.pgpainless.key.protection.SecretKeyRingProtector
|
import org.pgpainless.key.protection.SecretKeyRingProtector
|
||||||
import org.pgpainless.key.util.KeyRingUtils
|
import org.pgpainless.key.util.KeyRingUtils
|
||||||
|
import org.pgpainless.signature.builder.RevocationSignatureBuilder
|
||||||
import org.pgpainless.signature.builder.ThirdPartyCertificationSignatureBuilder
|
import org.pgpainless.signature.builder.ThirdPartyCertificationSignatureBuilder
|
||||||
import org.pgpainless.signature.builder.ThirdPartyDirectKeySignatureBuilder
|
import org.pgpainless.signature.builder.ThirdPartyDirectKeySignatureBuilder
|
||||||
import org.pgpainless.signature.subpackets.CertificationSubpackets
|
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
|
* 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
|
certificationType: CertificationType
|
||||||
) = CertificationOnUserId(userId, certificate, certificationType, api)
|
) = 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
|
* 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]
|
* 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?) =
|
fun certificate(certificate: PGPPublicKeyRing, trustworthiness: Trustworthiness?) =
|
||||||
DelegationOnCertificate(certificate, trustworthiness, api)
|
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(
|
class CertificationOnUserId(
|
||||||
private val userId: CharSequence,
|
private val userId: CharSequence,
|
||||||
private val certificate: OpenPGPCertificate,
|
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(
|
class DelegationOnCertificate(
|
||||||
private val certificate: OpenPGPCertificate,
|
private val certificate: OpenPGPCertificate,
|
||||||
private val trustworthiness: Trustworthiness?,
|
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.
|
* Result of a certification operation.
|
||||||
*
|
*
|
||||||
|
|
|
@ -83,6 +83,21 @@ constructor(
|
||||||
build(revokeeUserId.userId), signingKey.publicKey, revokeeUserId)
|
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 {
|
init {
|
||||||
hashedSubpackets.setRevocable(false)
|
hashedSubpackets.setRevocable(false)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue