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 deleted file mode 100644 index 5b6c6fe2..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/certification/CertifyCertificate.java +++ /dev/null @@ -1,298 +0,0 @@ -// 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 userIdOnCertificate(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); - assert (certificationPubKey != null); - if (!info.isKeyValidlyBound(certificationPubKey.getKeyID())) { - throw new KeyException.RevokedKeyException(fingerprint); - } - - if (!info.isUsableForThirdPartyCertification()) { - throw new KeyException.UnacceptableThirdPartyCertificationKeyException(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 deleted file mode 100644 index db2f4857..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/certification/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// 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/kotlin/org/pgpainless/key/certification/CertifyCertificate.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/certification/CertifyCertificate.kt new file mode 100644 index 00000000..a924e0f3 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/certification/CertifyCertificate.kt @@ -0,0 +1,228 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key.certification + +import org.bouncycastle.openpgp.PGPException +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.exception.KeyException.ExpiredKeyException +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.ThirdPartyCertificationSignatureBuilder +import org.pgpainless.signature.builder.ThirdPartyDirectKeySignatureBuilder +import org.pgpainless.signature.subpackets.CertificationSubpackets +import java.util.* + +/** + * 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. + */ +class CertifyCertificate { + + /** + * Create a certification over a User-Id. + * By default, this method will use [CertificationType.GENERIC] to create the signature. + * + * @param userId user-id to certify + * @param certificate certificate + * @return API + */ + fun userIdOnCertificate(userId: String, certificate: PGPPublicKeyRing): CertificationOnUserId = + userIdOnCertificate(userId, certificate, CertificationType.GENERIC) + + /** + * Create a certification of the given [CertificationType] over a User-Id. + * + * @param userid user-id to certify + * @param certificate certificate + * @param certificationType type of signature + * @return API + */ + fun userIdOnCertificate(userId: String, certificate: PGPPublicKeyRing, certificationType: CertificationType) = + 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 [certificate] method with [Trustworthiness] argument). + * + * @param certificate certificate + * @return API + */ + fun certificate(certificate: PGPPublicKeyRing): DelegationOnCertificate = + certificate(certificate, null) + + /** + * Create a delegation (direct key signature) containing a [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 + */ + fun certificate(certificate: PGPPublicKeyRing, trustworthiness: Trustworthiness?) = + DelegationOnCertificate(certificate, trustworthiness) + + class CertificationOnUserId( + val userId: String, + val certificate: PGPPublicKeyRing, + val 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 + */ + fun withKey(certificationKey: PGPSecretKeyRing, + protector: SecretKeyRingProtector): CertificationOnUserIdWithSubpackets { + + val secretKey = getCertifyingSecretKey(certificationKey) + val sigBuilder = ThirdPartyCertificationSignatureBuilder( + certificationType.asSignatureType(), secretKey, protector) + + return CertificationOnUserIdWithSubpackets(certificate, userId, sigBuilder) + } + } + + class CertificationOnUserIdWithSubpackets( + val certificate: PGPPublicKeyRing, + val userId: String, + val sigBuilder: ThirdPartyCertificationSignatureBuilder + ) { + + /** + * 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 + */ + fun buildWithSubpackets(subpacketCallback: CertificationSubpackets.Callback): CertificationResult { + sigBuilder.applyCallback(subpacketCallback) + return build() + } + + /** + * Build the certification signature. + * + * @return result + * @throws PGPException in case of an OpenPGP related error + */ + fun build(): CertificationResult { + val signature = sigBuilder.build(certificate, userId) + val certifiedCertificate = KeyRingUtils.injectCertification(certificate, userId, signature) + return CertificationResult(certifiedCertificate, signature) + } + } + + class DelegationOnCertificate( + val certificate: PGPPublicKeyRing, + val 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 + */ + fun withKey(certificationKey: PGPSecretKeyRing, + protector: SecretKeyRingProtector): DelegationOnCertificateWithSubpackets { + val secretKey = getCertifyingSecretKey(certificationKey) + val sigBuilder = ThirdPartyDirectKeySignatureBuilder(secretKey, protector) + if (trustworthiness != null) { + sigBuilder.hashedSubpackets.setTrust(true, trustworthiness.depth, trustworthiness.amount) + } + return DelegationOnCertificateWithSubpackets(certificate, sigBuilder) + } + } + + class DelegationOnCertificateWithSubpackets( + val certificate: PGPPublicKeyRing, + val sigBuilder: ThirdPartyDirectKeySignatureBuilder) { + + /** + * 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 + */ + fun buildWithSubpackets(subpacketsCallback: CertificationSubpackets.Callback): CertificationResult { + sigBuilder.applyCallback(subpacketsCallback) + return build() + } + + /** + * Build the delegation signature. + * + * @return result + * @throws PGPException in case of an OpenPGP related error + */ + fun build(): CertificationResult { + val delegatedKey = certificate.publicKey + val delegation = sigBuilder.build(delegatedKey) + val delegatedCertificate = KeyRingUtils.injectCertification(certificate, delegatedKey, delegation) + return CertificationResult(delegatedCertificate, delegation) + } + } + + /** + * Result of a certification operation. + * + * @param certifiedCertificate certificate which now contains the newly created signature + * @param certification the newly created signature + */ + data class CertificationResult( + val certifiedCertificate: PGPPublicKeyRing, + val certification: PGPSignature) + + companion object { + @JvmStatic + private fun getCertifyingSecretKey(certificationKey: PGPSecretKeyRing): PGPSecretKey { + val now = Date() + val info = PGPainless.inspectKeyRing(certificationKey, now) + + val fingerprint = info.fingerprint + val certificationPubKey = info.getPublicKey(fingerprint) + requireNotNull(certificationPubKey) { + "Primary key cannot be null." + } + if (!info.isKeyValidlyBound(certificationPubKey.keyID)) { + throw RevokedKeyException(fingerprint) + } + + if (!info.isUsableForThirdPartyCertification) { + throw KeyException.UnacceptableThirdPartyCertificationKeyException(fingerprint) + } + + val expirationDate = info.getExpirationDateForUse(KeyFlag.CERTIFY_OTHER) + if (expirationDate != null && expirationDate < now) { + throw ExpiredKeyException(fingerprint, expirationDate) + } + + return certificationKey.getSecretKey(certificationPubKey.keyID) + ?: throw MissingSecretKeyException(fingerprint, certificationPubKey.keyID) + } + } +} \ No newline at end of file