Implement certification of third party keys

This commit is contained in:
Paul Schaub 2022-05-07 14:17:44 +02:00
parent 73da2cc889
commit c1170773bc
5 changed files with 249 additions and 0 deletions

View File

@ -16,6 +16,7 @@ import org.pgpainless.decryption_verification.DecryptionBuilder;
import org.pgpainless.decryption_verification.DecryptionStream;
import org.pgpainless.encryption_signing.EncryptionBuilder;
import org.pgpainless.encryption_signing.EncryptionStream;
import org.pgpainless.key.certification.CertifyCertificate;
import org.pgpainless.key.generation.KeyRingBuilder;
import org.pgpainless.key.generation.KeyRingTemplates;
import org.pgpainless.key.info.KeyRingInfo;
@ -163,4 +164,8 @@ public final class PGPainless {
public static Policy getPolicy() {
return Policy.getInstance();
}
public static CertifyCertificate certifyCertificate() {
return new CertifyCertificate();
}
}

View File

@ -0,0 +1,46 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.algorithm;
import javax.annotation.Nonnull;
/**
* Subset of {@link SignatureType}, reduced to certification types.
*/
public enum CertificationType {
/**
* The issuer of this certification does not make any particular assertion as to how well the certifier has
* checked that the owner of the key is in fact the person described by the User ID.
*/
GENERIC(SignatureType.GENERIC_CERTIFICATION),
/**
* The issuer of this certification has not done any verification of the claim that the owner of this key is
* the User ID specified.
*/
NONE(SignatureType.NO_CERTIFICATION),
/**
* The issuer of this certification has done some casual verification of the claim of identity.
*/
CASUAL(SignatureType.CASUAL_CERTIFICATION),
/**
* The issuer of this certification has done some casual verification of the claim of identity.
*/
POSITIVE(SignatureType.POSITIVE_CERTIFICATION),
;
private final SignatureType signatureType;
CertificationType(@Nonnull SignatureType signatureType) {
this.signatureType = signatureType;
}
public @Nonnull SignatureType asSignatureType() {
return signatureType;
}
}

View File

@ -0,0 +1,123 @@
// 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.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSignature;
import org.pgpainless.PGPainless;
import org.pgpainless.algorithm.CertificationType;
import org.pgpainless.algorithm.KeyFlag;
import org.pgpainless.exception.KeyException;
import org.pgpainless.key.OpenPgpFingerprint;
import org.pgpainless.key.info.KeyRingInfo;
import org.pgpainless.key.protection.SecretKeyRingProtector;
import org.pgpainless.key.util.KeyRingUtils;
import org.pgpainless.signature.builder.ThirdPartyCertificationSignatureBuilder;
import org.pgpainless.signature.subpackets.CertificationSubpackets;
import org.pgpainless.util.DateUtil;
import java.util.Date;
public class CertifyCertificate {
CertifyUserId certifyUserId(PGPPublicKeyRing certificate, String userId) {
return new CertifyUserId(certificate, userId);
}
public static class CertifyUserId {
private final PGPPublicKeyRing certificate;
private final String userId;
private final CertificationType certificationType;
CertifyUserId(PGPPublicKeyRing certificate, String userId) {
this(certificate, userId, CertificationType.GENERIC);
}
CertifyUserId(PGPPublicKeyRing certificate, String userId, CertificationType certificationType) {
this.certificate = certificate;
this.userId = userId;
this.certificationType = certificationType;
}
CertifyUserIdWithSubpackets withKey(PGPSecretKeyRing certificationKey, SecretKeyRingProtector protector) throws PGPException {
Date now = DateUtil.now();
KeyRingInfo info = PGPainless.inspectKeyRing(certificationKey, now);
// We only support certification-capable primary keys
OpenPgpFingerprint fingerprint = info.getFingerprint();
PGPPublicKey certificationPubKey = info.getPublicKey(fingerprint);
if (!info.isKeyValidlyBound(certificationPubKey.getKeyID())) {
throw new KeyException.RevokedKeyException(fingerprint);
}
Date expirationDate = info.getExpirationDateForUse(KeyFlag.CERTIFY_OTHER);
if (expirationDate != null && expirationDate.before(now)) {
throw new KeyException.ExpiredKeyException(fingerprint, expirationDate);
}
PGPSecretKey secretKey = certificationKey.getSecretKey(certificationPubKey.getKeyID());
if (secretKey == null) {
throw new KeyException.MissingSecretKeyException(fingerprint, certificationPubKey.getKeyID());
}
ThirdPartyCertificationSignatureBuilder sigBuilder = new ThirdPartyCertificationSignatureBuilder(
certificationType.asSignatureType(), secretKey, protector);
return new CertifyUserIdWithSubpackets(certificate, userId, sigBuilder);
}
}
public static class CertifyUserIdWithSubpackets {
private final PGPPublicKeyRing certificate;
private final String userId;
private final ThirdPartyCertificationSignatureBuilder sigBuilder;
CertifyUserIdWithSubpackets(PGPPublicKeyRing certificate, String userId, ThirdPartyCertificationSignatureBuilder sigBuilder) {
this.certificate = certificate;
this.userId = userId;
this.sigBuilder = sigBuilder;
}
public CertifyUserIdResult withSubpackets(CertificationSubpackets.Callback subpacketCallback) throws PGPException {
sigBuilder.applyCallback(subpacketCallback);
return build();
}
public CertifyUserIdResult build() throws PGPException {
PGPSignature signature = sigBuilder.build(certificate, userId);
return new CertifyUserIdResult(certificate, userId, signature);
}
}
public static class CertifyUserIdResult {
private final PGPPublicKeyRing certificate;
private final String userId;
private final PGPSignature certification;
CertifyUserIdResult(PGPPublicKeyRing certificate, String userId, PGPSignature certification) {
this.certificate = certificate;
this.userId = userId;
this.certification = certification;
}
public PGPSignature getCertification() {
return certification;
}
public PGPPublicKeyRing getCertifiedCertificate() {
// inject the signature
PGPPublicKeyRing certified = KeyRingUtils.injectCertification(certificate, userId, certification);
return certified;
}
}
}

View File

@ -0,0 +1,8 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
/**
* API for key certifications.
*/
package org.pgpainless.key.certification;

View File

@ -0,0 +1,67 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.key.certification;
import static org.junit.jupiter.api.Assertions.assertEquals;
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.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.util.Arrays;
import org.junit.jupiter.api.Test;
import org.pgpainless.PGPainless;
import org.pgpainless.algorithm.SignatureType;
import org.pgpainless.key.protection.SecretKeyRingProtector;
import org.pgpainless.signature.consumer.SignatureVerifier;
import org.pgpainless.util.CollectionUtils;
import org.pgpainless.util.DateUtil;
public class CertifyCertificateTest {
@Test
public void testSuccessfulCertificationOfUserId() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException {
SecretKeyRingProtector protector = SecretKeyRingProtector.unprotectedKeys();
PGPSecretKeyRing alice = PGPainless.generateKeyRing().modernKeyRing("Alice <alice@pgpainless.org>", null);
String bobUserId = "Bob <bob@pgpainless.org>";
PGPSecretKeyRing bob = PGPainless.generateKeyRing().modernKeyRing(bobUserId, null);
PGPPublicKeyRing bobCertificate = PGPainless.extractCertificate(bob);
CertifyCertificate.CertifyUserIdResult result = PGPainless.certifyCertificate()
.certifyUserId(bobCertificate, bobUserId)
.withKey(alice, protector)
.build();
assertNotNull(result);
PGPSignature signature = result.getCertification();
assertNotNull(signature);
assertEquals(SignatureType.GENERIC_CERTIFICATION, SignatureType.valueOf(signature.getSignatureType()));
assertEquals(alice.getPublicKey().getKeyID(), signature.getKeyID());
assertTrue(SignatureVerifier.verifyUserIdCertification(
bobUserId, signature, alice.getPublicKey(), bob.getPublicKey(), PGPainless.getPolicy(), DateUtil.now()));
PGPPublicKeyRing bobCertified = result.getCertifiedCertificate();
PGPPublicKey bobCertifiedKey = bobCertified.getPublicKey();
// There are 2 sigs now, bobs own and alice'
assertEquals(2, CollectionUtils.iteratorToList(bobCertifiedKey.getSignaturesForID(bobUserId)).size());
List<PGPSignature> sigsByAlice = CollectionUtils.iteratorToList(
bobCertifiedKey.getSignaturesForKeyID(alice.getPublicKey().getKeyID()));
assertEquals(1, sigsByAlice.size());
assertEquals(signature, sigsByAlice.get(0));
assertFalse(Arrays.areEqual(bobCertificate.getEncoded(), bobCertified.getEncoded()));
}
}