diff --git a/pgpainless-core/src/main/java/org/pgpainless/PGPainless.java b/pgpainless-core/src/main/java/org/pgpainless/PGPainless.java index 827085ce..74a1f239 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/PGPainless.java +++ b/pgpainless-core/src/main/java/org/pgpainless/PGPainless.java @@ -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,13 @@ public final class PGPainless { public static Policy getPolicy() { return Policy.getInstance(); } + + /** + * Create different kinds of signatures on other keys. + * + * @return builder + */ + public static CertifyCertificate certify() { + return new CertifyCertificate(); + } } diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/CertificationType.java b/pgpainless-core/src/main/java/org/pgpainless/algorithm/CertificationType.java new file mode 100644 index 00000000..f5c8ec7e --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/algorithm/CertificationType.java @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// 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; + } +} diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/Trustworthiness.java b/pgpainless-core/src/main/java/org/pgpainless/algorithm/Trustworthiness.java new file mode 100644 index 00000000..26e66e9c --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/algorithm/Trustworthiness.java @@ -0,0 +1,188 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.algorithm; + +/** + * Facade class for {@link org.bouncycastle.bcpg.sig.TrustSignature}. + * A trust signature subpacket marks the trustworthiness of a certificate and defines its capabilities to act + * as a trusted introducer. + */ +public class Trustworthiness { + + private final int amount; + private final int depth; + + public static final int THRESHOLD_FULLY_CONVINCED = 120; // greater or equal is fully trusted + public static final int MARGINALLY_CONVINCED = 60; // default value for marginally convinced + public static final int NOT_TRUSTED = 0; // 0 is not trusted + + public Trustworthiness(int amount, int depth) { + this.amount = capAmount(amount); + this.depth = capDepth(depth); + } + + /** + * Get the trust amount. + * This value means how confident the issuer of the signature is in validity of the binding. + * + * @return trust amount + */ + public int getAmount() { + return amount; + } + + /** + * Get the depth of the trust signature. + * This value controls, whether the certificate can act as a trusted introducer. + * + * @return depth + */ + public int getDepth() { + return depth; + } + + /** + * Returns true, if the trust amount is equal to 0. + * This means the key is not trusted. + * + * Otherwise return false + * @return true if untrusted + */ + public boolean isNotTrusted() { + return getAmount() == NOT_TRUSTED; + } + + /** + * Return true if the certificate is at least marginally trusted. + * That is the case, if the trust amount is greater than 0. + * + * @return true if the cert is at least marginally trusted + */ + public boolean isMarginallyTrusted() { + return getAmount() > NOT_TRUSTED; + } + + /** + * Return true if the certificate is fully trusted. That is the case if the trust amount is + * greater than or equal to 120. + * + * @return true if the cert is fully trusted + */ + public boolean isFullyTrusted() { + return getAmount() >= THRESHOLD_FULLY_CONVINCED; + } + + /** + * Return true, if the cert is an introducer. That is the case if the depth is greater 0. + * + * @return true if introducer + */ + public boolean isIntroducer() { + return getDepth() >= 1; + } + + /** + * Return true, if the certified cert can introduce certificates with trust depth of
otherDepth
. + * + * @param otherDepth other certifications trust depth + * @return true if the cert can introduce the other + */ + public boolean canIntroduce(int otherDepth) { + return getDepth() > otherDepth; + } + + /** + * Return true, if the certified cert can introduce certificates with the given
other
trust depth. + * + * @param other other certificates trust depth + * @return true if the cert can introduce the other + */ + public boolean canIntroduce(Trustworthiness other) { + return canIntroduce(other.getDepth()); + } + + /** + * This means that we are fully convinced of the trustworthiness of the key. + * + * @return builder + */ + public static Builder fullyTrusted() { + return new Builder(THRESHOLD_FULLY_CONVINCED); + } + + /** + * This means that we are marginally (partially) convinced of the trustworthiness of the key. + * + * @return builder + */ + public static Builder marginallyTrusted() { + return new Builder(MARGINALLY_CONVINCED); + } + + /** + * This means that we do not trust the key. + * Can be used to overwrite previous trust. + * + * @return builder + */ + public static Builder untrusted() { + return new Builder(NOT_TRUSTED); + } + + public static final class Builder { + + private final int amount; + + private Builder(int amount) { + this.amount = amount; + } + + /** + * The key is a trusted introducer (depth 1). + * Certifications made by this key are considered trustworthy. + * + * @return trust + */ + public Trustworthiness introducer() { + return new Trustworthiness(amount, 1); + } + + /** + * The key is a meta introducer (depth 2). + * This key can introduce trusted introducers of depth 1. + * + * @return trust + */ + public Trustworthiness metaIntroducer() { + return new Trustworthiness(amount, 2); + } + + /** + * The key is a meta introducer of depth
n
. + * This key can introduce meta introducers of depth
n - 1
. + * + * @param n depth + * @return trust + */ + public Trustworthiness metaIntroducerOfDepth(int n) { + return new Trustworthiness(amount, n); + } + } + + private static int capAmount(int amount) { + if (amount < 0 || amount > 255) { + throw new IllegalArgumentException("Trust amount MUST be a value between 0 and 255"); + } + return amount; + } + + private static int capDepth(int depth) { + if (depth < 0 || depth > 255) { + throw new IllegalArgumentException("Trust depth MUST be a value between 0 and 255"); + } + return depth; + } + +} diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/certification/CertifyCertificate.java b/pgpainless-core/src/main/java/org/pgpainless/key/certification/CertifyCertificate.java new file mode 100644 index 00000000..a4f37b66 --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/key/certification/CertifyCertificate.java @@ -0,0 +1,293 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// 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.algorithm.Trustworthiness; +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.ThirdPartyDirectKeySignatureBuilder; +import org.pgpainless.signature.builder.ThirdPartyCertificationSignatureBuilder; +import org.pgpainless.signature.subpackets.CertificationSubpackets; +import org.pgpainless.util.DateUtil; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Date; + +/** + * API for creating certifications and delegations (Signatures) on keys. + * This API can be used to sign another persons OpenPGP key. + * + * A certification over a user-id is thereby used to attest, that the user believes that the user-id really belongs + * to the owner of the certificate. + * A delegation over a key can be used to delegate trust by marking the certificate as a trusted introducer. + */ +public class CertifyCertificate { + + /** + * Create a certification over a User-Id. + * By default, this method will use {@link CertificationType#GENERIC} to create the signature. + * If you need to create another type of certification, use + * {@link #userIdOnCertificate(String, PGPPublicKeyRing, CertificationType)} instead. + * + * @param userId user-id to certify + * @param certificate certificate + * @return API + */ + public CertificationOnUserId userIdOnCertificate(@Nonnull String userId, + @Nonnull PGPPublicKeyRing certificate) { + return new CertificationOnUserId(userId, certificate, CertificationType.GENERIC); + } + + /** + * Create a certification of the given {@link CertificationType} over a User-Id. + * + * @param userid user-id to certify + * @param certificate certificate + * @param certificationType type of signature + * @return API + */ + public CertificationOnUserId userIdOnCertificate(@Nonnull String userid, + @Nonnull PGPPublicKeyRing certificate, + @Nonnull CertificationType certificationType) { + return new CertificationOnUserId(userid, certificate, certificationType); + } + + /** + * Create a delegation (direct key signature) over a certificate. + * This can be used to mark a certificate as a trusted introducer + * (see {@link #certificate(PGPPublicKeyRing, Trustworthiness)}). + * + * @param certificate certificate + * @return API + */ + public DelegationOnCertificate certificate(@Nonnull PGPPublicKeyRing certificate) { + return certificate(certificate, null); + } + + /** + * Create a delegation (direct key signature) containing a {@link org.bouncycastle.bcpg.sig.TrustSignature} packet + * over a certificate. + * This can be used to mark a certificate as a trusted introducer. + * + * @param certificate certificate + * @param trustworthiness trustworthiness of the certificate + * @return API + */ + public DelegationOnCertificate certificate(@Nonnull PGPPublicKeyRing certificate, + @Nullable Trustworthiness trustworthiness) { + return new DelegationOnCertificate(certificate, trustworthiness); + } + + public static class CertificationOnUserId { + + private final PGPPublicKeyRing certificate; + private final String userId; + private final CertificationType certificationType; + + CertificationOnUserId(@Nonnull String userId, + @Nonnull PGPPublicKeyRing certificate, + @Nonnull CertificationType certificationType) { + this.userId = userId; + this.certificate = certificate; + this.certificationType = certificationType; + } + + /** + * Create the certification using the given key. + * + * @param certificationKey key used to create the certification + * @param protector protector to unlock the certification key + * @return API + * @throws PGPException in case of an OpenPGP related error + */ + public CertificationOnUserIdWithSubpackets withKey(@Nonnull PGPSecretKeyRing certificationKey, + @Nonnull SecretKeyRingProtector protector) + throws PGPException { + PGPSecretKey secretKey = getCertifyingSecretKey(certificationKey); + + ThirdPartyCertificationSignatureBuilder sigBuilder = new ThirdPartyCertificationSignatureBuilder( + certificationType.asSignatureType(), secretKey, protector); + + return new CertificationOnUserIdWithSubpackets(certificate, userId, sigBuilder); + } + } + + public static class CertificationOnUserIdWithSubpackets { + + private final PGPPublicKeyRing certificate; + private final String userId; + private final ThirdPartyCertificationSignatureBuilder sigBuilder; + + CertificationOnUserIdWithSubpackets(@Nonnull PGPPublicKeyRing certificate, + @Nonnull String userId, + @Nonnull ThirdPartyCertificationSignatureBuilder sigBuilder) { + this.certificate = certificate; + this.userId = userId; + this.sigBuilder = sigBuilder; + } + + /** + * Apply the given signature subpackets and build the certification. + * + * @param subpacketCallback callback to modify the signatures subpackets + * @return result + * @throws PGPException in case of an OpenPGP related error + */ + public CertificationResult buildWithSubpackets(@Nonnull CertificationSubpackets.Callback subpacketCallback) + throws PGPException { + sigBuilder.applyCallback(subpacketCallback); + return build(); + } + + /** + * Build the certification signature. + * + * @return result + * @throws PGPException in case of an OpenPGP related error + */ + public CertificationResult build() throws PGPException { + PGPSignature signature = sigBuilder.build(certificate, userId); + PGPPublicKeyRing certifiedCertificate = KeyRingUtils.injectCertification(certificate, userId, signature); + return new CertificationResult(certifiedCertificate, signature); + } + } + + public static class DelegationOnCertificate { + + private final PGPPublicKeyRing certificate; + private final Trustworthiness trustworthiness; + + DelegationOnCertificate(@Nonnull PGPPublicKeyRing certificate, + @Nullable Trustworthiness trustworthiness) { + this.certificate = certificate; + this.trustworthiness = trustworthiness; + } + + /** + * Build the delegation using the given certification key. + * + * @param certificationKey key to create the certification with + * @param protector protector to unlock the certification key + * @return API + * @throws PGPException in case of an OpenPGP related error + */ + public DelegationOnCertificateWithSubpackets withKey(@Nonnull PGPSecretKeyRing certificationKey, + @Nonnull SecretKeyRingProtector protector) + throws PGPException { + PGPSecretKey secretKey = getCertifyingSecretKey(certificationKey); + + ThirdPartyDirectKeySignatureBuilder sigBuilder = new ThirdPartyDirectKeySignatureBuilder(secretKey, protector); + if (trustworthiness != null) { + sigBuilder.getHashedSubpackets().setTrust(true, trustworthiness.getDepth(), trustworthiness.getAmount()); + } + return new DelegationOnCertificateWithSubpackets(certificate, sigBuilder); + } + } + + public static class DelegationOnCertificateWithSubpackets { + + private final PGPPublicKeyRing certificate; + private final ThirdPartyDirectKeySignatureBuilder sigBuilder; + + DelegationOnCertificateWithSubpackets(@Nonnull PGPPublicKeyRing certificate, + @Nonnull ThirdPartyDirectKeySignatureBuilder sigBuilder) { + this.certificate = certificate; + this.sigBuilder = sigBuilder; + } + + /** + * Apply the given signature subpackets and build the delegation signature. + * + * @param subpacketsCallback callback to modify the signatures subpackets + * @return result + * @throws PGPException in case of an OpenPGP related error + */ + public CertificationResult buildWithSubpackets(@Nonnull CertificationSubpackets.Callback subpacketsCallback) + throws PGPException { + sigBuilder.applyCallback(subpacketsCallback); + return build(); + } + + /** + * Build the delegation signature. + * + * @return result + * @throws PGPException in case of an OpenPGP related error + */ + public CertificationResult build() throws PGPException { + PGPPublicKey delegatedKey = certificate.getPublicKey(); + PGPSignature delegation = sigBuilder.build(delegatedKey); + PGPPublicKeyRing delegatedCertificate = KeyRingUtils.injectCertification(certificate, delegatedKey, delegation); + return new CertificationResult(delegatedCertificate, delegation); + } + } + + public static class CertificationResult { + + private final PGPPublicKeyRing certificate; + private final PGPSignature certification; + + CertificationResult(@Nonnull PGPPublicKeyRing certificate, @Nonnull PGPSignature certification) { + this.certificate = certificate; + this.certification = certification; + } + + /** + * Return the signature. + * + * @return signature + */ + @Nonnull + public PGPSignature getCertification() { + return certification; + } + + /** + * Return the certificate, which now contains the signature. + * + * @return certificate + signature + */ + @Nonnull + public PGPPublicKeyRing getCertifiedCertificate() { + return certificate; + } + } + + private static PGPSecretKey getCertifyingSecretKey(PGPSecretKeyRing certificationKey) { + 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()); + } + return secretKey; + } +} diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/certification/package-info.java b/pgpainless-core/src/main/java/org/pgpainless/key/certification/package-info.java new file mode 100644 index 00000000..db2f4857 --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/key/certification/package-info.java @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +/** + * API for key certifications. + */ +package org.pgpainless.key.certification; diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.java b/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.java index f5a5292c..7418401f 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.java @@ -56,7 +56,7 @@ import org.pgpainless.key.protection.fixes.S2KUsageFix; import org.pgpainless.key.protection.passphrase_provider.SolitaryPassphraseProvider; import org.pgpainless.key.util.KeyRingUtils; import org.pgpainless.key.util.RevocationAttributes; -import org.pgpainless.signature.builder.DirectKeySignatureBuilder; +import org.pgpainless.signature.builder.DirectKeySelfSignatureBuilder; import org.pgpainless.signature.builder.RevocationSignatureBuilder; import org.pgpainless.signature.builder.SelfSignatureBuilder; import org.pgpainless.signature.subpackets.RevocationSignatureSubpackets; @@ -612,7 +612,7 @@ public class SecretKeyRingEditor implements SecretKeyRingEditorInterface { PGPPublicKey publicKey = primaryKey.getPublicKey(); final Date keyCreationTime = publicKey.getCreationTime(); - DirectKeySignatureBuilder builder = new DirectKeySignatureBuilder(primaryKey, secretKeyRingProtector, prevDirectKeySig); + DirectKeySelfSignatureBuilder builder = new DirectKeySelfSignatureBuilder(primaryKey, secretKeyRingProtector, prevDirectKeySig); builder.applyCallback(new SelfSignatureSubpackets.Callback() { @Override public void modifyHashedSubpackets(SelfSignatureSubpackets hashedSubpackets) { diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/builder/DirectKeySignatureBuilder.java b/pgpainless-core/src/main/java/org/pgpainless/signature/builder/DirectKeySelfSignatureBuilder.java similarity index 65% rename from pgpainless-core/src/main/java/org/pgpainless/signature/builder/DirectKeySignatureBuilder.java rename to pgpainless-core/src/main/java/org/pgpainless/signature/builder/DirectKeySelfSignatureBuilder.java index caf08710..cf99f8d6 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/signature/builder/DirectKeySignatureBuilder.java +++ b/pgpainless-core/src/main/java/org/pgpainless/signature/builder/DirectKeySelfSignatureBuilder.java @@ -10,18 +10,19 @@ import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPSecretKey; import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.PGPSignatureGenerator; import org.pgpainless.algorithm.SignatureType; import org.pgpainless.key.protection.SecretKeyRingProtector; import org.pgpainless.signature.subpackets.SelfSignatureSubpackets; -public class DirectKeySignatureBuilder extends AbstractSignatureBuilder { +public class DirectKeySelfSignatureBuilder extends AbstractSignatureBuilder { - public DirectKeySignatureBuilder(PGPSecretKey certificationKey, SecretKeyRingProtector protector, PGPSignature archetypeSignature) + public DirectKeySelfSignatureBuilder(PGPSecretKey certificationKey, SecretKeyRingProtector protector, PGPSignature archetypeSignature) throws PGPException { super(certificationKey, protector, archetypeSignature); } - public DirectKeySignatureBuilder(PGPSecretKey signingKey, SecretKeyRingProtector protector) throws PGPException { + public DirectKeySelfSignatureBuilder(PGPSecretKey signingKey, SecretKeyRingProtector protector) throws PGPException { super(SignatureType.DIRECT_KEY, signingKey, protector); } @@ -41,8 +42,12 @@ public class DirectKeySignatureBuilder extends AbstractSignatureBuilder +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.signature.builder; + +import javax.annotation.Nullable; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.PGPSignatureGenerator; +import org.pgpainless.algorithm.SignatureType; +import org.pgpainless.key.protection.SecretKeyRingProtector; +import org.pgpainless.signature.subpackets.CertificationSubpackets; + +public class ThirdPartyDirectKeySignatureBuilder extends AbstractSignatureBuilder { + + public ThirdPartyDirectKeySignatureBuilder(PGPSecretKey certificationKey, SecretKeyRingProtector protector, PGPSignature archetypeSignature) + throws PGPException { + super(certificationKey, protector, archetypeSignature); + } + + public ThirdPartyDirectKeySignatureBuilder(PGPSecretKey signingKey, SecretKeyRingProtector protector) throws PGPException { + super(SignatureType.DIRECT_KEY, signingKey, protector); + } + + public CertificationSubpackets getHashedSubpackets() { + return hashedSubpackets; + } + + public CertificationSubpackets getUnhashedSubpackets() { + return unhashedSubpackets; + } + + public void applyCallback(@Nullable CertificationSubpackets.Callback callback) { + if (callback != null) { + callback.modifyHashedSubpackets(getHashedSubpackets()); + callback.modifyUnhashedSubpackets(getUnhashedSubpackets()); + } + } + + public PGPSignature build(PGPPublicKey key) throws PGPException { + PGPSignatureGenerator signatureGenerator = buildAndInitSignatureGenerator(); + if (key.getKeyID() != publicSigningKey.getKeyID()) { + return signatureGenerator.generateCertification(publicSigningKey, key); + } else { + return signatureGenerator.generateCertification(key); + } + } + + @Override + protected boolean isValidSignatureType(SignatureType type) { + return type == SignatureType.DIRECT_KEY; + } +} diff --git a/pgpainless-core/src/test/java/org/pgpainless/algorithm/TrustworthinessTest.java b/pgpainless-core/src/test/java/org/pgpainless/algorithm/TrustworthinessTest.java new file mode 100644 index 00000000..bf87ed65 --- /dev/null +++ b/pgpainless-core/src/test/java/org/pgpainless/algorithm/TrustworthinessTest.java @@ -0,0 +1,87 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.algorithm; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +public class TrustworthinessTest { + + @Test + public void fullyTrustedIntroducer() { + Trustworthiness it = Trustworthiness.fullyTrusted().introducer(); + assertTrue(it.isFullyTrusted()); + assertFalse(it.isNotTrusted()); + + assertTrue(it.isIntroducer()); + assertFalse(it.canIntroduce(it)); + } + + @Test + public void marginallyTrustedIntroducer() { + Trustworthiness it = Trustworthiness.marginallyTrusted().introducer(); + assertFalse(it.isFullyTrusted()); + assertTrue(it.isMarginallyTrusted()); + assertFalse(it.isNotTrusted()); + + assertTrue(it.isIntroducer()); + assertFalse(it.canIntroduce(2)); + } + + @Test + public void nonTrustedIntroducer() { + Trustworthiness it = Trustworthiness.untrusted().introducer(); + assertTrue(it.isNotTrusted()); + assertFalse(it.isMarginallyTrusted()); + assertFalse(it.isFullyTrusted()); + + assertTrue(it.isIntroducer()); + } + + @Test + public void trustedMetaIntroducer() { + Trustworthiness it = Trustworthiness.fullyTrusted().metaIntroducer(); + assertTrue(it.isFullyTrusted()); + assertTrue(it.isIntroducer()); + + Trustworthiness that = Trustworthiness.fullyTrusted().introducer(); + assertTrue(it.canIntroduce(that)); + assertFalse(that.canIntroduce(it)); + } + + @Test + public void invalidArguments() { + assertThrows(IllegalArgumentException.class, () -> new Trustworthiness(300, 1)); + assertThrows(IllegalArgumentException.class, () -> new Trustworthiness(60, 300)); + assertThrows(IllegalArgumentException.class, () -> new Trustworthiness(-4, 1)); + assertThrows(IllegalArgumentException.class, () -> new Trustworthiness(120, -1)); + } + + @Test + public void inBetweenValues() { + Trustworthiness it = new Trustworthiness(30, 1); + assertTrue(it.isMarginallyTrusted()); + assertFalse(it.isFullyTrusted()); + + it = new Trustworthiness(140, 1); + assertTrue(it.isFullyTrusted()); + } + + @Test + public void depthHierarchyTest() { + Trustworthiness l1 = Trustworthiness.fullyTrusted().metaIntroducerOfDepth(1); + Trustworthiness l2 = Trustworthiness.fullyTrusted().metaIntroducerOfDepth(2); + Trustworthiness l3 = Trustworthiness.fullyTrusted().metaIntroducerOfDepth(3); + + assertTrue(l3.canIntroduce(l2)); + assertTrue(l3.canIntroduce(l1)); + assertTrue(l2.canIntroduce(l1)); + assertFalse(l1.canIntroduce(l2)); + assertFalse(l1.canIntroduce(l3)); + } +} diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/certification/CertifyCertificateTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/certification/CertifyCertificateTest.java new file mode 100644 index 00000000..3dbc4988 --- /dev/null +++ b/pgpainless-core/src/test/java/org/pgpainless/key/certification/CertifyCertificateTest.java @@ -0,0 +1,108 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// 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.bcpg.sig.TrustSignature; +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.algorithm.Trustworthiness; +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 testUserIdCertification() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException { + SecretKeyRingProtector protector = SecretKeyRingProtector.unprotectedKeys(); + PGPSecretKeyRing alice = PGPainless.generateKeyRing().modernKeyRing("Alice "); + String bobUserId = "Bob "; + PGPSecretKeyRing bob = PGPainless.generateKeyRing().modernKeyRing(bobUserId); + + PGPPublicKeyRing bobCertificate = PGPainless.extractCertificate(bob); + + CertifyCertificate.CertificationResult result = PGPainless.certify() + .userIdOnCertificate(bobUserId, bobCertificate) + .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 sigsByAlice = CollectionUtils.iteratorToList( + bobCertifiedKey.getSignaturesForKeyID(alice.getPublicKey().getKeyID())); + assertEquals(1, sigsByAlice.size()); + assertEquals(signature, sigsByAlice.get(0)); + + assertFalse(Arrays.areEqual(bobCertificate.getEncoded(), bobCertified.getEncoded())); + } + + @Test + public void testKeyDelegation() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException { + SecretKeyRingProtector protector = SecretKeyRingProtector.unprotectedKeys(); + PGPSecretKeyRing alice = PGPainless.generateKeyRing().modernKeyRing("Alice "); + PGPSecretKeyRing bob = PGPainless.generateKeyRing().modernKeyRing("Bob "); + + PGPPublicKeyRing bobCertificate = PGPainless.extractCertificate(bob); + + CertifyCertificate.CertificationResult result = PGPainless.certify() + .certificate(bobCertificate, Trustworthiness.fullyTrusted().introducer()) + .withKey(alice, protector) + .build(); + + assertNotNull(result); + PGPSignature signature = result.getCertification(); + assertNotNull(signature); + assertEquals(SignatureType.DIRECT_KEY, SignatureType.valueOf(signature.getSignatureType())); + assertEquals(alice.getPublicKey().getKeyID(), signature.getKeyID()); + TrustSignature trustSignaturePacket = signature.getHashedSubPackets().getTrust(); + assertNotNull(trustSignaturePacket); + Trustworthiness trustworthiness = new Trustworthiness(trustSignaturePacket.getTrustAmount(), trustSignaturePacket.getDepth()); + assertTrue(trustworthiness.isFullyTrusted()); + assertTrue(trustworthiness.isIntroducer()); + assertFalse(trustworthiness.canIntroduce(1)); + + assertTrue(SignatureVerifier.verifyDirectKeySignature( + signature, alice.getPublicKey(), bob.getPublicKey(), PGPainless.getPolicy(), DateUtil.now())); + + PGPPublicKeyRing bobCertified = result.getCertifiedCertificate(); + PGPPublicKey bobCertifiedKey = bobCertified.getPublicKey(); + + List sigsByAlice = CollectionUtils.iteratorToList( + bobCertifiedKey.getSignaturesForKeyID(alice.getPublicKey().getKeyID())); + assertEquals(1, sigsByAlice.size()); + assertEquals(signature, sigsByAlice.get(0)); + + assertFalse(Arrays.areEqual(bobCertificate.getEncoded(), bobCertified.getEncoded())); + } +} diff --git a/pgpainless-core/src/test/java/org/pgpainless/signature/builder/DirectKeySignatureBuilderTest.java b/pgpainless-core/src/test/java/org/pgpainless/signature/builder/ThirdPartyDirectKeySignatureBuilderTest.java similarity index 96% rename from pgpainless-core/src/test/java/org/pgpainless/signature/builder/DirectKeySignatureBuilderTest.java rename to pgpainless-core/src/test/java/org/pgpainless/signature/builder/ThirdPartyDirectKeySignatureBuilderTest.java index 945a06a5..46dc1ac5 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/signature/builder/DirectKeySignatureBuilderTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/signature/builder/ThirdPartyDirectKeySignatureBuilderTest.java @@ -29,14 +29,14 @@ import org.pgpainless.key.util.KeyRingUtils; import org.pgpainless.signature.subpackets.SelfSignatureSubpackets; import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil; -public class DirectKeySignatureBuilderTest { +public class ThirdPartyDirectKeySignatureBuilderTest { @Test public void testDirectKeySignatureBuilding() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, InterruptedException { PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing() .modernKeyRing("Alice"); - DirectKeySignatureBuilder dsb = new DirectKeySignatureBuilder( + DirectKeySelfSignatureBuilder dsb = new DirectKeySelfSignatureBuilder( secretKeys.getSecretKey(), SecretKeyRingProtector.unprotectedKeys());