diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/AEADAlgorithm.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/AEADAlgorithm.kt index d6f5423d..6e06abe3 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/AEADAlgorithm.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/AEADAlgorithm.kt @@ -8,11 +8,12 @@ import org.bouncycastle.openpgp.api.MessageEncryptionMechanism /** * AEAD Algorithm. + * * @param algorithmId numeric algorithm id * @param ivLength length of the initialization vector * @param tagLength length of the tag - * - * @see [RFC9580 - AEAD Algorithms](https://www.rfc-editor.org/rfc/rfc9580.html#name-aead-algorithms) + * @see + * [RFC9580 - AEAD Algorithms](https://www.rfc-editor.org/rfc/rfc9580.html#name-aead-algorithms) */ enum class AEADAlgorithm(val algorithmId: Int, val ivLength: Int, val tagLength: Int) { @@ -41,8 +42,8 @@ enum class AEADAlgorithm(val algorithmId: Int, val ivLength: Int, val tagLength: ; /** - * Return a [MessageEncryptionMechanism] instance representing AEAD using this algorithm and - * the given [SymmetricKeyAlgorithm]. + * Return a [MessageEncryptionMechanism] instance representing AEAD using this algorithm and the + * given [SymmetricKeyAlgorithm]. * * @param ciphermode symmetric key algorithm * @return MessageEncryptionMechanism representing aead(this, ciphermode) @@ -53,8 +54,8 @@ enum class AEADAlgorithm(val algorithmId: Int, val ivLength: Int, val tagLength: companion object { /** - * Parse an [AEADAlgorithm] from an algorithm id. - * If no matching [AEADAlgorithm] is known, return `null`. + * Parse an [AEADAlgorithm] from an algorithm id. If no matching [AEADAlgorithm] is known, + * return `null`. * * @param id algorithm id * @return aeadAlgorithm or null @@ -65,8 +66,8 @@ enum class AEADAlgorithm(val algorithmId: Int, val ivLength: Int, val tagLength: } /** - * Parse an [AEADAlgorithm] from an algorithm id. - * If no matching [AEADAlgorithm] is known, throw a [NoSuchElementException]. + * Parse an [AEADAlgorithm] from an algorithm id. If no matching [AEADAlgorithm] is known, + * throw a [NoSuchElementException]. * * @param id algorithm id * @return aeadAlgorithm diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/OpenPGPCertificateExtensions.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/OpenPGPCertificateExtensions.kt index 008ed758..886f6830 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/OpenPGPCertificateExtensions.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/OpenPGPCertificateExtensions.kt @@ -9,6 +9,11 @@ import org.bouncycastle.openpgp.api.OpenPGPCertificate import org.bouncycastle.openpgp.api.OpenPGPCertificate.OpenPGPComponentKey import org.pgpainless.algorithm.OpenPGPKeyVersion +/** + * Return the [OpenPGPComponentKey] that issued the given [PGPOnePassSignature]. + * + * @param ops one pass signature + */ fun OpenPGPCertificate.getSigningKeyFor(ops: PGPOnePassSignature): OpenPGPComponentKey? = this.getKey(ops.keyIdentifier) diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/OpenPGPImplementationExtensions.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/OpenPGPImplementationExtensions.kt index 18a0a737..a546c1a1 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/OpenPGPImplementationExtensions.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/OpenPGPImplementationExtensions.kt @@ -11,10 +11,20 @@ import org.bouncycastle.openpgp.api.OpenPGPImplementation import org.bouncycastle.openpgp.operator.PGPDataEncryptorBuilder import org.bouncycastle.openpgp.operator.PGPDigestCalculator +/** + * Return a [PGPDigestCalculator] that is based on [HashAlgorithmTags.SHA1], used for key checksum + * calculations. + */ fun OpenPGPImplementation.checksumCalculator(): PGPDigestCalculator { return pgpDigestCalculatorProvider().get(HashAlgorithmTags.SHA1) } +/** + * Return a [PGPDataEncryptorBuilder] for the given [MessageEncryptionMechanism]. + * + * @param mechanism + * @return data encryptor builder + */ fun OpenPGPImplementation.pgpDataEncryptorBuilder( mechanism: MessageEncryptionMechanism ): PGPDataEncryptorBuilder { @@ -32,7 +42,5 @@ fun OpenPGPImplementation.pgpDataEncryptorBuilder( it.setUseV5AEAD() } } - - return it } } diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/OpenPGPKeyExtensions.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/OpenPGPKeyExtensions.kt index 06a7e8ed..cbdf97ef 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/OpenPGPKeyExtensions.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/OpenPGPKeyExtensions.kt @@ -5,17 +5,25 @@ package org.pgpainless.bouncycastle.extensions import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData -import org.bouncycastle.openpgp.PGPSignature import org.bouncycastle.openpgp.api.OpenPGPKey import org.bouncycastle.openpgp.api.OpenPGPKey.OpenPGPPrivateKey import org.bouncycastle.openpgp.api.OpenPGPKey.OpenPGPSecretKey import org.pgpainless.util.Passphrase +/** + * Return the [OpenPGPSecretKey] that can be used to decrypt the given [PGPPublicKeyEncryptedData]. + * + * @param pkesk public-key encrypted session-key packet + * @return secret key or null if no matching secret key was found + */ fun OpenPGPKey.getSecretKeyFor(pkesk: PGPPublicKeyEncryptedData): OpenPGPSecretKey? = this.getSecretKey(pkesk.keyIdentifier) -fun OpenPGPKey.getSecretKeyFor(signature: PGPSignature): OpenPGPSecretKey? = - this.getSecretKey(signature.fingerprint!!.keyIdentifier) - -fun OpenPGPKey.OpenPGPSecretKey.unlock(passphrase: Passphrase): OpenPGPPrivateKey = +/** + * Unlock the [OpenPGPSecretKey], returning the unlocked [OpenPGPPrivateKey]. + * + * @param passphrase passphrase to unlock the key + * @return unlocked [OpenPGPPrivateKey] + */ +fun OpenPGPSecretKey.unlock(passphrase: Passphrase): OpenPGPPrivateKey = this.unlock(passphrase.getChars()) diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/PGPKeyRingExtensions.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/PGPKeyRingExtensions.kt index 50633fcf..10b90d64 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/PGPKeyRingExtensions.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/PGPKeyRingExtensions.kt @@ -16,11 +16,22 @@ import org.pgpainless.PGPainless import org.pgpainless.key.OpenPgpFingerprint import org.pgpainless.key.SubkeyIdentifier -/** Return true, if this [PGPKeyRing] contains the subkey identified by the [SubkeyIdentifier]. */ +/** + * Return true, if this [PGPKeyRing] contains the subkey identified by the [SubkeyIdentifier]. + * + * @param subkeyIdentifier subkey identifier + * @return true if the [PGPKeyRing] contains the [SubkeyIdentifier] + */ fun PGPKeyRing.matches(subkeyIdentifier: SubkeyIdentifier): Boolean = this.publicKey.keyIdentifier.matches(subkeyIdentifier.certificateIdentifier) && this.getPublicKey(subkeyIdentifier.componentKeyIdentifier) != null +/** + * Return true, if this [PGPKeyRing] contains the given [componentKey]. + * + * @param componentKey component key + * @return true if the [PGPKeyRing] contains the [componentKey] + */ fun PGPKeyRing.matches(componentKey: OpenPGPComponentKey): Boolean = this.matches(SubkeyIdentifier(componentKey)) @@ -60,13 +71,37 @@ fun PGPKeyRing.hasPublicKey(fingerprint: OpenPgpFingerprint): Boolean = fun PGPKeyRing.getPublicKey(fingerprint: OpenPgpFingerprint): PGPPublicKey? = this.getPublicKey(fingerprint.keyIdentifier) +/** + * Return the [PGPPublicKey] with the given [keyIdentifier], or throw a [NoSuchElementException] if + * no matching public key was found. + * + * @param keyIdentifier key identifier + * @return public key + * @throws NoSuchElementException if no matching public key was found + */ fun PGPKeyRing.requirePublicKey(keyIdentifier: KeyIdentifier): PGPPublicKey = getPublicKey(keyIdentifier) ?: throw NoSuchElementException("OpenPGP key does not contain key with id $keyIdentifier.") +/** + * Return the [PGPPublicKey] with the given key-id, or throw a [NoSuchElementException] if no + * matching public key was found. + * + * @param keyId key id + * @return public key + * @throws NoSuchElementException if no matching public key was found + */ @Deprecated("Pass in a KeyIdentifier instead.") fun PGPKeyRing.requirePublicKey(keyId: Long): PGPPublicKey = requirePublicKey(KeyIdentifier(keyId)) +/** + * Return the [PGPPublicKey] with the given [fingerprint], or throw a [NoSuchElementException] if no + * matching public key was found. + * + * @param fingerprint key fingerprint + * @return public key + * @throws NoSuchElementException if no matching public key was found + */ fun PGPKeyRing.requirePublicKey(fingerprint: OpenPgpFingerprint): PGPPublicKey = requirePublicKey(fingerprint.keyIdentifier) @@ -90,9 +125,21 @@ val PGPKeyRing.openPgpFingerprint: OpenPgpFingerprint /** Return this OpenPGP key as an ASCII armored String. */ fun PGPKeyRing.toAsciiArmor(): String = PGPainless.asciiArmor(this) +/** + * Convert the given [PGPKeyRing] into an [OpenPGPCertificate]. + * + * @return certificate + */ @Deprecated("Use toOpenPGPCertificate(implementation) instead.") fun PGPKeyRing.toOpenPGPCertificate(): OpenPGPCertificate = toOpenPGPCertificate(PGPainless.getInstance().implementation) +/** + * Convert the given [PGPKeyRing] into an [OpenPGPCertificate] using the given + * [OpenPGPImplementation]. + * + * @param implementation OpenPGP implementation + * @return certificate + */ fun PGPKeyRing.toOpenPGPCertificate(implementation: OpenPGPImplementation): OpenPGPCertificate = OpenPGPCertificate(this, implementation) diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/PGPSecretKeyRingExtensions.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/PGPSecretKeyRingExtensions.kt index 9566988f..44570f17 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/PGPSecretKeyRingExtensions.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/PGPSecretKeyRingExtensions.kt @@ -98,12 +98,30 @@ fun PGPSecretKeyRing.getSecretKeyFor(signature: PGPSignature): PGPSecretKey? = fun PGPSecretKeyRing.getSecretKeyFor(onePassSignature: PGPOnePassSignature): PGPSecretKey? = this.getSecretKey(onePassSignature.keyIdentifier) +/** + * Return the [PGPSecretKey] that can be used to decrypt the given [PGPPublicKeyEncryptedData] + * packet. + * + * @param pkesk public-key encrypted session-key packet + * @return secret-key or null if no matching secret key was found + */ fun PGPSecretKeyRing.getSecretKeyFor(pkesk: PGPPublicKeyEncryptedData): PGPSecretKey? = this.getSecretKey(pkesk.keyIdentifier) +/** + * Convert the [PGPSecretKeyRing] into an [OpenPGPKey]. + * + * @return key + */ @Deprecated("Use toOpenPGPKey(implementation) instead.") fun PGPSecretKeyRing.toOpenPGPKey(): OpenPGPKey = toOpenPGPKey(PGPainless.getInstance().implementation) +/** + * Convert the [PGPSecretKeyRing] into an [OpenPGPKey] using the given [OpenPGPImplementation]. + * + * @param implementation openpgp implementation + * @return key + */ fun PGPSecretKeyRing.toOpenPGPKey(implementation: OpenPGPImplementation): OpenPGPKey = OpenPGPKey(this, implementation) diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/PGPSignatureExtensions.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/PGPSignatureExtensions.kt index 2b3d08c2..8ffd81a1 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/PGPSignatureExtensions.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/PGPSignatureExtensions.kt @@ -57,17 +57,29 @@ val PGPSignature.issuerKeyId: Long } } -/** Return true, if the signature was likely issued by a key with the given fingerprint. */ +/** + * Return true, if the signature was likely issued by a key with the given fingerprint. + * + * @param fingerprint fingerprint of the key + * @return true if signature was likely issued by the key + */ fun PGPSignature.wasIssuedBy(fingerprint: OpenPgpFingerprint): Boolean = wasIssuedBy(fingerprint.keyIdentifier) /** - * Return true, if the signature was likely issued by a key with the given fingerprint. + * Return true, if the signature was likely issued by the given key. * * @param key key + * @return true if signature was likely issued by the key */ fun PGPSignature.wasIssuedBy(key: PGPPublicKey): Boolean = wasIssuedBy(key.keyIdentifier) +/** + * Return true, if the signature was likely issued by a key with the given identifier. + * + * @param keyIdentifier key identifier + * @return true if signature was likely issued by the key + */ fun PGPSignature.wasIssuedBy(keyIdentifier: KeyIdentifier): Boolean = KeyIdentifier.matches(this.keyIdentifiers, keyIdentifier, true) @@ -87,6 +99,13 @@ val PGPSignature.isHardRevocation else -> false // Not a revocation } +/** + * Assert that the signatures creation time falls into the period between [notBefore] and + * [notAfter]. + * + * @param notBefore lower bound. If null, do not check the lower bound + * @param notAfter upper bound. If null, do not check the upper bound + */ fun PGPSignature.assertCreatedInBounds(notBefore: Date?, notAfter: Date?) { if (notBefore != null && creationTime < notBefore) { throw SignatureValidationException( @@ -102,19 +121,35 @@ fun PGPSignature.assertCreatedInBounds(notBefore: Date?, notAfter: Date?) { } } +/** + * Deduce a [RevocationState] from the signature. Non-revocation signatures result in + * [RevocationState.notRevoked]. Hard revocations result in [RevocationState.hardRevoked], while + * soft revocations return [RevocationState.softRevoked] + * + * @return revocation state + */ fun PGPSignature?.toRevocationState() = if (this == null) RevocationState.notRevoked() else if (isHardRevocation) RevocationState.hardRevoked() else RevocationState.softRevoked(creationTime) +/** The signatures issuer fingerprint as [OpenPgpFingerprint]. */ val PGPSignature.fingerprint: OpenPgpFingerprint? get() = SignatureSubpacketsUtil.getIssuerFingerprintAsOpenPgpFingerprint(this) +/** The signatures [PublicKeyAlgorithm]. */ val PGPSignature.publicKeyAlgorithm: PublicKeyAlgorithm get() = PublicKeyAlgorithm.requireFromId(keyAlgorithm) +/** The signatures [HashAlgorithm]. */ val PGPSignature.signatureHashAlgorithm: HashAlgorithm get() = HashAlgorithm.requireFromId(hashAlgorithm) +/** + * Return true if the signature has the given [SignatureType]. + * + * @param type signature type + * @return true if the signature type matches the signatures type + */ fun PGPSignature.isOfType(type: SignatureType): Boolean = SignatureType.fromCode(signatureType) == type diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/PreferredAEADCipherSuitesExtensions.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/PreferredAEADCipherSuitesExtensions.kt index fec2dcc1..0d7f8539 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/PreferredAEADCipherSuitesExtensions.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/PreferredAEADCipherSuitesExtensions.kt @@ -7,6 +7,7 @@ package org.pgpainless.bouncycastle.extensions import org.bouncycastle.bcpg.sig.PreferredAEADCiphersuites import org.pgpainless.algorithm.AEADCipherMode +/** Convert the [PreferredAEADCiphersuites] packet into a [Set] of [AEADCipherMode]. */ fun PreferredAEADCiphersuites?.toAEADCipherModes(): Set { return this?.algorithms?.asSequence()?.map { AEADCipherMode(it) }?.toSet() ?: setOf() } diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/PreferredAlgorithmsExtensions.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/PreferredAlgorithmsExtensions.kt index e78e7568..1fadd60b 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/PreferredAlgorithmsExtensions.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/PreferredAlgorithmsExtensions.kt @@ -9,16 +9,19 @@ import org.pgpainless.algorithm.CompressionAlgorithm import org.pgpainless.algorithm.HashAlgorithm import org.pgpainless.algorithm.SymmetricKeyAlgorithm +/** Convert the [PreferredAlgorithms] packet into a [Set] of [HashAlgorithm] preferences. */ fun PreferredAlgorithms?.toHashAlgorithms(): Set { return this?.preferences?.asSequence()?.map { HashAlgorithm.requireFromId(it) }?.toSet() ?: setOf() } +/** Convert the [PreferredAlgorithms] packet into a [Set] of [SymmetricKeyAlgorithm] preferences. */ fun PreferredAlgorithms?.toSymmetricKeyAlgorithms(): Set { return this?.preferences?.asSequence()?.map { SymmetricKeyAlgorithm.requireFromId(it) }?.toSet() ?: setOf() } +/** Convert the [PreferredAlgorithms] packet into a [Set] of [CompressionAlgorithm] preferences. */ fun PreferredAlgorithms?.toCompressionAlgorithms(): Set { return this?.preferences?.asSequence()?.map { CompressionAlgorithm.requireFromId(it) }?.toSet() ?: setOf() diff --git a/pgpainless-core/src/test/kotlin/org/pgpainless/bouncycastle/extensions/PGPSecretKeyRingExtensionsTest.kt b/pgpainless-core/src/test/kotlin/org/pgpainless/bouncycastle/extensions/PGPSecretKeyRingExtensionsTest.kt index d1aa7682..cb3004ca 100644 --- a/pgpainless-core/src/test/kotlin/org/pgpainless/bouncycastle/extensions/PGPSecretKeyRingExtensionsTest.kt +++ b/pgpainless-core/src/test/kotlin/org/pgpainless/bouncycastle/extensions/PGPSecretKeyRingExtensionsTest.kt @@ -4,18 +4,12 @@ package org.pgpainless.bouncycastle.extensions -import java.io.ByteArrayOutputStream import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertNotNull -import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows -import org.pgpainless.PGPainless -import org.pgpainless.encryption_signing.ProducerOptions -import org.pgpainless.encryption_signing.SigningOptions import org.pgpainless.key.TestKeys -import org.pgpainless.key.protection.SecretKeyRingProtector class PGPSecretKeyRingExtensionsTest { @@ -46,23 +40,4 @@ class PGPSecretKeyRingExtensionsTest { } assertThrows { key.requireSecretKey(TestKeys.ROMEO_FINGERPRINT) } } - - @Test - fun testGetSecretKeyForSignature() { - val key = TestKeys.getEmilKey() - val signer = - PGPainless.getInstance() - .generateMessage() - .onOutputStream(ByteArrayOutputStream()) - .withOptions( - ProducerOptions.sign( - SigningOptions.get() - .addDetachedSignature(SecretKeyRingProtector.unprotectedKeys(), key))) - signer.write("Hello, World!\n".toByteArray()) - signer.close() - val sig = signer.result.detachedSignatures.first().value.first() - - assertNotNull(key.getSecretKeyFor(sig)) - assertNull(TestKeys.getRomeoSecretKeyRing().getSecretKeyFor(sig)) - } }