mirror of
https://github.com/pgpainless/pgpainless.git
synced 2025-12-12 23:31:08 +01:00
Finish Policy conversion and move kotlin classes to src/kotlin/
This commit is contained in:
parent
b68061373d
commit
e57e74163c
36 changed files with 371 additions and 896 deletions
175
pgpainless-core/src/main/kotlin/org/pgpainless/PGPainless.kt
Normal file
175
pgpainless-core/src/main/kotlin/org/pgpainless/PGPainless.kt
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless
|
||||
|
||||
import org.bouncycastle.openpgp.PGPKeyRing
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing
|
||||
import org.bouncycastle.openpgp.PGPSignature
|
||||
import org.pgpainless.decryption_verification.DecryptionBuilder
|
||||
import org.pgpainless.encryption_signing.EncryptionBuilder
|
||||
import org.pgpainless.key.certification.CertifyCertificate
|
||||
import org.pgpainless.key.generation.KeyRingBuilder
|
||||
import org.pgpainless.key.generation.KeyRingTemplates
|
||||
import org.pgpainless.key.info.KeyRingInfo
|
||||
import org.pgpainless.key.modification.secretkeyring.SecretKeyRingEditor
|
||||
import org.pgpainless.key.parsing.KeyRingReader
|
||||
import org.pgpainless.key.util.KeyRingUtils
|
||||
import org.pgpainless.policy.Policy
|
||||
import org.pgpainless.util.ArmorUtils
|
||||
import java.io.OutputStream
|
||||
import java.util.*
|
||||
|
||||
class PGPainless private constructor() {
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Generate a fresh OpenPGP key ring from predefined templates.
|
||||
* @return templates
|
||||
*/
|
||||
@JvmStatic
|
||||
fun generateKeyRing() = KeyRingTemplates()
|
||||
|
||||
/**
|
||||
* Build a custom OpenPGP key ring.
|
||||
*
|
||||
* @return builder
|
||||
*/
|
||||
@JvmStatic
|
||||
fun buildKeyRing() = KeyRingBuilder()
|
||||
|
||||
/**
|
||||
* Read an existing OpenPGP key ring.
|
||||
* @return builder
|
||||
*/
|
||||
@JvmStatic
|
||||
fun readKeyRing() = KeyRingReader()
|
||||
|
||||
/**
|
||||
* Extract a public key certificate from a secret key.
|
||||
*
|
||||
* @param secretKey secret key
|
||||
* @return public key certificate
|
||||
*/
|
||||
@JvmStatic
|
||||
fun extractCertificate(secretKey: PGPSecretKeyRing) =
|
||||
KeyRingUtils.publicKeyRingFrom(secretKey)
|
||||
|
||||
/**
|
||||
* Merge two copies of the same certificate (e.g. an old copy, and one retrieved from a key server) together.
|
||||
*
|
||||
* @param originalCopy local, older copy of the cert
|
||||
* @param updatedCopy updated, newer copy of the cert
|
||||
* @return merged certificate
|
||||
* @throws PGPException in case of an error
|
||||
*/
|
||||
@JvmStatic
|
||||
fun mergeCertificate(originalCopy: PGPPublicKeyRing,
|
||||
updatedCopy: PGPPublicKeyRing) =
|
||||
PGPPublicKeyRing.join(originalCopy, updatedCopy)
|
||||
|
||||
/**
|
||||
* Wrap a key or certificate in ASCII armor.
|
||||
*
|
||||
* @param key key or certificate
|
||||
* @return ascii armored string
|
||||
*
|
||||
* @throws IOException in case of an error during the armoring process
|
||||
*/
|
||||
@JvmStatic
|
||||
fun asciiArmor(key: PGPKeyRing) =
|
||||
if (key is PGPSecretKeyRing)
|
||||
ArmorUtils.toAsciiArmoredString(key)
|
||||
else
|
||||
ArmorUtils.toAsciiArmoredString(key as PGPPublicKeyRing)
|
||||
|
||||
/**
|
||||
* Wrap a key of certificate in ASCII armor and write the result into the given [OutputStream].
|
||||
*
|
||||
* @param key key or certificate
|
||||
* @param outputStream output stream
|
||||
*
|
||||
* @throws IOException in case of an error during the armoring process
|
||||
*/
|
||||
@JvmStatic
|
||||
fun asciiArmor(key: PGPKeyRing, outputStream: OutputStream) {
|
||||
val armorOut = ArmorUtils.toAsciiArmoredStream(key, outputStream)
|
||||
key.encode(armorOut)
|
||||
armorOut.close()
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap the detached signature in ASCII armor.
|
||||
*
|
||||
* @param signature detached signature
|
||||
* @return ascii armored string
|
||||
*
|
||||
* @throws IOException in case of an error during the armoring process
|
||||
*/
|
||||
@JvmStatic
|
||||
fun asciiArmor(signature: PGPSignature) = ArmorUtils.toAsciiArmoredString(signature)
|
||||
|
||||
/**
|
||||
* Create an [EncryptionBuilder], which can be used to encrypt and/or sign data using OpenPGP.
|
||||
*
|
||||
* @return builder
|
||||
*/
|
||||
@JvmStatic
|
||||
fun encryptAndOrSign() = EncryptionBuilder()
|
||||
|
||||
/**
|
||||
* Create a [DecryptionBuilder], which can be used to decrypt and/or verify data using OpenPGP.
|
||||
*
|
||||
* @return builder
|
||||
*/
|
||||
@JvmStatic
|
||||
fun decryptAndOrVerify() = DecryptionBuilder()
|
||||
|
||||
/**
|
||||
* Make changes to a secret key at the given reference time.
|
||||
* This method can be used to change key expiration dates and passphrases, or add/revoke user-ids and subkeys.
|
||||
* <p>
|
||||
* After making the desired changes in the builder, the modified key can be extracted using {@link SecretKeyRingEditorInterface#done()}.
|
||||
*
|
||||
* @param secretKeys secret key ring
|
||||
* @param referenceTime reference time used as signature creation date
|
||||
* @return builder
|
||||
*/
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun modifyKeyRing(secretKey: PGPSecretKeyRing, referenceTime: Date = Date()) =
|
||||
SecretKeyRingEditor(secretKey, referenceTime)
|
||||
|
||||
/**
|
||||
* Quickly access information about a [org.bouncycastle.openpgp.PGPPublicKeyRing] / [PGPSecretKeyRing].
|
||||
* This method can be used to determine expiration dates, key flags and other information about a key at a specific time.
|
||||
*
|
||||
* @param keyRing key ring
|
||||
* @param referenceTime date of inspection
|
||||
* @return access object
|
||||
*/
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun inspectKeyRing(key: PGPKeyRing, referenceTime: Date = Date()) =
|
||||
KeyRingInfo(key, referenceTime)
|
||||
|
||||
/**
|
||||
* Access, and make changes to PGPainless policy on acceptable/default algorithms etc.
|
||||
*
|
||||
* @return policy
|
||||
*/
|
||||
@JvmStatic
|
||||
fun getPolicy() = Policy.getInstance()
|
||||
|
||||
/**
|
||||
* Create different kinds of signatures on other keys.
|
||||
*
|
||||
* @return builder
|
||||
*/
|
||||
@JvmStatic
|
||||
fun certify() = CertifyCertificate()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <info@pgpainless.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.algorithm
|
||||
|
||||
enum class AEADAlgorithm(
|
||||
val algorithmId: Int,
|
||||
val ivLength: Int,
|
||||
val tagLength: Int) {
|
||||
EAX(1, 16, 16),
|
||||
OCB(2, 15, 16),
|
||||
GCM(3, 12, 16),
|
||||
;
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun fromId(id: Int): AEADAlgorithm? {
|
||||
return values().firstOrNull {
|
||||
algorithm -> algorithm.algorithmId == id
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun requireFromId(id: Int): AEADAlgorithm {
|
||||
return fromId(id) ?:
|
||||
throw NoSuchElementException("No AEADAlgorithm found for id $id")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.algorithm
|
||||
|
||||
class AlgorithmSuite(
|
||||
symmetricKeyAlgorithms: List<SymmetricKeyAlgorithm>,
|
||||
hashAlgorithms: List<HashAlgorithm>,
|
||||
compressionAlgorithms: List<CompressionAlgorithm>) {
|
||||
|
||||
val symmetricKeyAlgorithms: Set<SymmetricKeyAlgorithm> = symmetricKeyAlgorithms.toSet()
|
||||
val hashAlgorithms: Set<HashAlgorithm> = hashAlgorithms.toSet()
|
||||
val compressionAlgorithms: Set<CompressionAlgorithm> = compressionAlgorithms.toSet()
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmStatic
|
||||
val defaultSymmetricKeyAlgorithms = listOf(
|
||||
SymmetricKeyAlgorithm.AES_256,
|
||||
SymmetricKeyAlgorithm.AES_192,
|
||||
SymmetricKeyAlgorithm.AES_128)
|
||||
|
||||
@JvmStatic
|
||||
val defaultHashAlgorithms = listOf(
|
||||
HashAlgorithm.SHA512,
|
||||
HashAlgorithm.SHA384,
|
||||
HashAlgorithm.SHA256,
|
||||
HashAlgorithm.SHA224)
|
||||
|
||||
@JvmStatic
|
||||
val defaultCompressionAlgorithms = listOf(
|
||||
CompressionAlgorithm.ZLIB,
|
||||
CompressionAlgorithm.BZIP2,
|
||||
CompressionAlgorithm.ZIP,
|
||||
CompressionAlgorithm.UNCOMPRESSED)
|
||||
|
||||
@JvmStatic
|
||||
val defaultAlgorithmSuite = AlgorithmSuite(
|
||||
defaultSymmetricKeyAlgorithms,
|
||||
defaultHashAlgorithms,
|
||||
defaultCompressionAlgorithms)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.algorithm
|
||||
|
||||
enum class CertificationType(
|
||||
val signatureType: SignatureType
|
||||
) {
|
||||
/**
|
||||
* 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),
|
||||
;
|
||||
|
||||
fun asSignatureType() = signatureType
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.algorithm
|
||||
|
||||
/**
|
||||
* Enumeration of possible compression algorithms.
|
||||
*
|
||||
* See also [RFC4880 - Compression Algorithm Tags](https://tools.ietf.org/html/rfc4880#section-9.3)
|
||||
*/
|
||||
enum class CompressionAlgorithm(val algorithmId: Int) {
|
||||
|
||||
UNCOMPRESSED(0),
|
||||
ZIP(1),
|
||||
ZLIB(2),
|
||||
BZIP2(3),
|
||||
;
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Return the [CompressionAlgorithm] value that corresponds to the provided numerical id.
|
||||
* If an invalid id is provided, null is returned.
|
||||
*
|
||||
* @param id id
|
||||
* @return compression algorithm
|
||||
*/
|
||||
@JvmStatic
|
||||
fun fromId(id: Int): CompressionAlgorithm? {
|
||||
return values().firstOrNull {
|
||||
c -> c.algorithmId == id
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the [CompressionAlgorithm] value that corresponds to the provided numerical id.
|
||||
* If an invalid id is provided, throw an [NoSuchElementException].
|
||||
*
|
||||
* @param id id
|
||||
* @return compression algorithm
|
||||
* @throws NoSuchElementException in case of an unmapped id
|
||||
*/
|
||||
@JvmStatic
|
||||
fun requireFromId(id: Int): CompressionAlgorithm {
|
||||
return fromId(id) ?:
|
||||
throw NoSuchElementException("No CompressionAlgorithm found for id $id")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.algorithm
|
||||
|
||||
enum class DocumentSignatureType(val signatureType: SignatureType) {
|
||||
|
||||
/**
|
||||
* Signature is calculated over the unchanged binary data.
|
||||
*/
|
||||
BINARY_DOCUMENT(SignatureType.BINARY_DOCUMENT),
|
||||
|
||||
/**
|
||||
* The signature is calculated over the text data with its line endings
|
||||
* converted to `<CR><LF>`.
|
||||
*/
|
||||
CANONICAL_TEXT_DOCUMENT(SignatureType.CANONICAL_TEXT_DOCUMENT),
|
||||
;
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.algorithm
|
||||
|
||||
enum class EncryptionPurpose {
|
||||
/**
|
||||
* The stream will encrypt communication that goes over the wire.
|
||||
* E.g. EMail, Chat...
|
||||
*/
|
||||
COMMUNICATIONS,
|
||||
/**
|
||||
* The stream will encrypt data at rest.
|
||||
* E.g. Encrypted backup...
|
||||
*/
|
||||
STORAGE,
|
||||
/**
|
||||
* The stream will use keys with either flags to encrypt the data.
|
||||
*/
|
||||
ANY
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.algorithm
|
||||
|
||||
/**
|
||||
* An enumeration of features that may be set in the feature subpacket.
|
||||
*
|
||||
* See [RFC4880: Features](https://tools.ietf.org/html/rfc4880#section-5.2.3.24)
|
||||
*/
|
||||
enum class Feature(val featureId: Byte) {
|
||||
|
||||
/**
|
||||
* Support for Symmetrically Encrypted Integrity Protected Data Packets (version 1) using Modification
|
||||
* Detection Code Packets.
|
||||
*
|
||||
* See [RFC-4880 §5.14: Modification Detection Code Packet](https://tools.ietf.org/html/rfc4880#section-5.14)
|
||||
*/
|
||||
MODIFICATION_DETECTION(0x01),
|
||||
|
||||
/**
|
||||
* Support for Authenticated Encryption with Additional Data (AEAD).
|
||||
* If a key announces this feature, it signals support for consuming AEAD Encrypted Data Packets.
|
||||
*
|
||||
* NOTE: PGPAINLESS DOES NOT YET SUPPORT THIS FEATURE!!!
|
||||
* NOTE: This value is currently RESERVED.
|
||||
*
|
||||
* See [AEAD Encrypted Data Packet](https://openpgp-wg.gitlab.io/rfc4880bis/#name-aead-encrypted-data-packet-)
|
||||
*/
|
||||
GNUPG_AEAD_ENCRYPTED_DATA(0x02),
|
||||
|
||||
/**
|
||||
* If a key announces this feature, it is a version 5 public key.
|
||||
* The version 5 format is similar to the version 4 format except for the addition of a count for the key material.
|
||||
* This count helps to parse secret key packets (which are an extension of the public key packet format) in the case
|
||||
* of an unknown algorithm.
|
||||
* In addition, fingerprints of version 5 keys are calculated differently from version 4 keys.
|
||||
*
|
||||
* NOTE: PGPAINLESS DOES NOT YET SUPPORT THIS FEATURE!!!
|
||||
* NOTE: This value is currently RESERVED.
|
||||
*
|
||||
* See [Public-Key Packet Formats](https://openpgp-wg.gitlab.io/rfc4880bis/#name-public-key-packet-formats)
|
||||
*/
|
||||
GNUPG_VERSION_5_PUBLIC_KEY(0x04),
|
||||
|
||||
/**
|
||||
* Support for Symmetrically Encrypted Integrity Protected Data packet version 2.
|
||||
*
|
||||
* See [crypto-refresh-06 §5.13.2. Version 2 Sym. Encrypted Integrity Protected Data Packet Format](https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-06.html#version-two-seipd)
|
||||
*/
|
||||
MODIFICATION_DETECTION_2(0x08),
|
||||
;
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun fromId(id: Byte): Feature? {
|
||||
return values().firstOrNull {
|
||||
f -> f.featureId == id
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun requireFromId(id: Byte): Feature {
|
||||
return fromId(id) ?:
|
||||
throw NoSuchElementException("Unknown feature id encountered: $id")
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun fromBitmask(bitmask: Int): List<Feature> {
|
||||
return values().filter {
|
||||
it.featureId.toInt() and bitmask != 0
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun toBitmask(vararg features: Feature): Byte {
|
||||
return features.map { it.featureId.toInt() }
|
||||
.reduceOrNull { mask, f -> mask or f }?.toByte()
|
||||
?: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.algorithm
|
||||
|
||||
/**
|
||||
* An enumeration of different hashing algorithms.
|
||||
*
|
||||
* See [RFC4880: Hash Algorithms](https://tools.ietf.org/html/rfc4880#section-9.4)
|
||||
*/
|
||||
enum class HashAlgorithm(val algorithmId: Int, val algorithmName: String) {
|
||||
|
||||
@Deprecated("MD5 is deprecated")
|
||||
MD5 (1, "MD5"),
|
||||
SHA1 (2, "SHA1"),
|
||||
RIPEMD160 (3, "RIPEMD160"),
|
||||
SHA256 (8, "SHA256"),
|
||||
SHA384 (9, "SHA384"),
|
||||
SHA512 (10, "SHA512"),
|
||||
SHA224 (11, "SHA224"),
|
||||
SHA3_256 (12, "SHA3-256"),
|
||||
SHA3_512 (14, "SHA3-512"),
|
||||
;
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Return the [HashAlgorithm] value that corresponds to the provided algorithm id.
|
||||
* If an invalid algorithm id was provided, null is returned.
|
||||
*
|
||||
* @param id numeric id
|
||||
* @return enum value
|
||||
*/
|
||||
@JvmStatic
|
||||
fun fromId(id: Int): HashAlgorithm? {
|
||||
return values().firstOrNull {
|
||||
h -> h.algorithmId == id
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the [HashAlgorithm] value that corresponds to the provided algorithm id.
|
||||
* If an invalid algorithm id was provided, throw a [NoSuchElementException].
|
||||
*
|
||||
* @param id algorithm id
|
||||
* @return enum value
|
||||
* @throws NoSuchElementException in case of an unknown algorithm id
|
||||
*/
|
||||
@JvmStatic
|
||||
fun requireFromId(id: Int): HashAlgorithm {
|
||||
return fromId(id) ?:
|
||||
throw NoSuchElementException("No HashAlgorithm found for id $id")
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the [HashAlgorithm] value that corresponds to the provided name.
|
||||
* If an invalid algorithm name was provided, null is returned.
|
||||
*
|
||||
* See [RFC4880: §9.4 Hash Algorithms](https://datatracker.ietf.org/doc/html/rfc4880#section-9.4)
|
||||
* for a list of algorithms and names.
|
||||
*
|
||||
* @param name text name
|
||||
* @return enum value
|
||||
*/
|
||||
@JvmStatic
|
||||
fun fromName(name: String): HashAlgorithm? {
|
||||
return name.uppercase().let { algoName ->
|
||||
values().firstOrNull {
|
||||
it.algorithmName == algoName
|
||||
} ?: values().firstOrNull {
|
||||
it.algorithmName == algoName.replace("-", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.algorithm
|
||||
|
||||
enum class KeyFlag(val flag: Int) {
|
||||
|
||||
/**
|
||||
* This key may be used to certify third-party keys.
|
||||
*/
|
||||
CERTIFY_OTHER (1),
|
||||
|
||||
/**
|
||||
* This key may be used to sign data.
|
||||
*/
|
||||
SIGN_DATA (2),
|
||||
|
||||
/**
|
||||
* This key may be used to encrypt communications.
|
||||
*/
|
||||
ENCRYPT_COMMS (4),
|
||||
|
||||
/**
|
||||
* This key may be used to encrypt storage.
|
||||
*/
|
||||
ENCRYPT_STORAGE(8),
|
||||
|
||||
/**
|
||||
* The private component of this key may have been split by a secret-sharing mechanism.
|
||||
*/
|
||||
SPLIT (16),
|
||||
|
||||
/**
|
||||
* This key may be used for authentication.
|
||||
*/
|
||||
AUTHENTICATION (32),
|
||||
|
||||
/**
|
||||
* The private component of this key may be in the possession of more than one person.
|
||||
*/
|
||||
SHARED (128),
|
||||
;
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Convert a bitmask into a list of [KeyFlags][KeyFlag].
|
||||
*
|
||||
* @param bitmask bitmask
|
||||
* @return list of key flags encoded by the bitmask
|
||||
*/
|
||||
@JvmStatic
|
||||
fun fromBitmask(bitmask: Int): List<KeyFlag> {
|
||||
return values().filter {
|
||||
it.flag and bitmask != 0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a list of {@link KeyFlag KeyFlags} into a bitmask.
|
||||
*
|
||||
* @param flags list of flags
|
||||
* @return bitmask
|
||||
*/
|
||||
@JvmStatic
|
||||
fun toBitmask(vararg flags: KeyFlag): Int {
|
||||
return flags.map { it.flag }.reduceOrNull { mask, f -> mask or f }
|
||||
?: 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the provided bitmask has the bit for the provided flag set.
|
||||
* Return false if the mask does not contain the flag.
|
||||
*
|
||||
* @param mask bitmask
|
||||
* @param flag flag to be tested for
|
||||
* @return true if flag is set, false otherwise
|
||||
*/
|
||||
@JvmStatic
|
||||
fun hasKeyFlag(mask: Int, flag: KeyFlag): Boolean {
|
||||
return mask and flag.flag == flag.flag
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun containsAny(mask: Int, vararg flags: KeyFlag): Boolean {
|
||||
return flags.any {
|
||||
hasKeyFlag(mask, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.algorithm
|
||||
|
||||
enum class OpenPgpPacket(val tag: Int) {
|
||||
PKESK(1),
|
||||
SIG(2),
|
||||
SKESK(3),
|
||||
OPS(4),
|
||||
SK(5),
|
||||
PK(6),
|
||||
SSK(7),
|
||||
COMP(8),
|
||||
SED(9),
|
||||
MARKER(10),
|
||||
LIT(11),
|
||||
TRUST(12),
|
||||
UID(13),
|
||||
PSK(14),
|
||||
UATTR(17),
|
||||
SEIPD(18),
|
||||
MDC(19),
|
||||
|
||||
EXP_1(60),
|
||||
EXP_2(61),
|
||||
EXP_3(62),
|
||||
EXP_4(63),
|
||||
;
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun fromTag(tag: Int): OpenPgpPacket? {
|
||||
return values().firstOrNull {
|
||||
it.tag == tag
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun requireFromTag(tag: Int): OpenPgpPacket {
|
||||
return fromTag(tag) ?:
|
||||
throw NoSuchElementException("No OpenPGP packet known for tag $tag")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.algorithm
|
||||
|
||||
/**
|
||||
* Enumeration of public key algorithms as defined in RFC4880.
|
||||
*
|
||||
* See [RFC4880: Public-Key Algorithms](https://tools.ietf.org/html/rfc4880#section-9.1)
|
||||
*/
|
||||
enum class PublicKeyAlgorithm(
|
||||
val algorithmId: Int,
|
||||
val signingCapable: Boolean,
|
||||
val encryptionCapable: Boolean) {
|
||||
|
||||
/**
|
||||
* RSA capable of encryption and signatures.
|
||||
*/
|
||||
RSA_GENERAL (1, true, true),
|
||||
|
||||
/**
|
||||
* RSA with usage encryption.
|
||||
*
|
||||
* @deprecated see <a href="https://tools.ietf.org/html/rfc4880#section-13.5">Deprecation notice</a>
|
||||
*/
|
||||
@Deprecated("RSA_ENCRYPT is deprecated in favor of RSA_GENERAL",
|
||||
ReplaceWith("RSA_GENERAL"))
|
||||
RSA_ENCRYPT (2, false, true),
|
||||
|
||||
/**
|
||||
* RSA with usage of creating signatures.
|
||||
*
|
||||
* @deprecated see <a href="https://tools.ietf.org/html/rfc4880#section-13.5">Deprecation notice</a>
|
||||
*/
|
||||
@Deprecated("RSA_SIGN is deprecated in favor of RSA_GENERAL",
|
||||
ReplaceWith("RSA_GENERAL"))
|
||||
RSA_SIGN (3, true, false),
|
||||
|
||||
/**
|
||||
* ElGamal with usage encryption.
|
||||
*/
|
||||
ELGAMAL_ENCRYPT (16, false, true),
|
||||
|
||||
/**
|
||||
* Digital Signature Algorithm.
|
||||
*/
|
||||
DSA (17, true, false),
|
||||
|
||||
/**
|
||||
* Elliptic Curve Diffie-Hellman.
|
||||
*/
|
||||
ECDH (18, false, true),
|
||||
|
||||
/**
|
||||
* Elliptic Curve Digital Signature Algorithm.
|
||||
*/
|
||||
ECDSA (19, true, false),
|
||||
|
||||
/**
|
||||
* ElGamal General.
|
||||
*
|
||||
* @deprecated see <a href="https://tools.ietf.org/html/rfc4880#section-13.8">Deprecation notice</a>
|
||||
*/
|
||||
@Deprecated("ElGamal is deprecated")
|
||||
ELGAMAL_GENERAL (20, true, true),
|
||||
|
||||
/**
|
||||
* Diffie-Hellman key exchange algorithm.
|
||||
*/
|
||||
DIFFIE_HELLMAN (21, false, true),
|
||||
|
||||
/**
|
||||
* Digital Signature Algorithm based on twisted Edwards Curves.
|
||||
*/
|
||||
EDDSA (22, true, false),
|
||||
;
|
||||
|
||||
fun isSigningCapable(): Boolean = signingCapable
|
||||
fun isEncryptionCapable(): Boolean = encryptionCapable
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun fromId(id: Int): PublicKeyAlgorithm? {
|
||||
return values().firstOrNull {
|
||||
it.algorithmId == id
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun requireFromId(id: Int): PublicKeyAlgorithm {
|
||||
return fromId(id) ?:
|
||||
throw NoSuchElementException("No PublicKeyAlgorithm found for id $id")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.algorithm
|
||||
|
||||
import org.pgpainless.util.DateUtil
|
||||
import java.lang.AssertionError
|
||||
import java.util.*
|
||||
import kotlin.NoSuchElementException
|
||||
|
||||
class RevocationState private constructor(
|
||||
val type: RevocationStateType,
|
||||
private val _date: Date?): Comparable<RevocationState> {
|
||||
|
||||
val date: Date
|
||||
get() {
|
||||
if (!isSoftRevocation()) {
|
||||
throw NoSuchElementException("RevocationStateType is not equal to 'softRevoked'. Cannot extract date.")
|
||||
}
|
||||
return _date!!
|
||||
}
|
||||
|
||||
private constructor(type: RevocationStateType): this(type, null)
|
||||
|
||||
fun isSoftRevocation() = type == RevocationStateType.softRevoked
|
||||
fun isHardRevocation() = type == RevocationStateType.hardRevoked
|
||||
fun isNotRevoked() = type == RevocationStateType.notRevoked
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun notRevoked() = RevocationState(RevocationStateType.notRevoked)
|
||||
|
||||
@JvmStatic
|
||||
fun softRevoked(date: Date) = RevocationState(RevocationStateType.softRevoked, date)
|
||||
|
||||
@JvmStatic
|
||||
fun hardRevoked() = RevocationState(RevocationStateType.hardRevoked)
|
||||
}
|
||||
|
||||
override fun compareTo(other: RevocationState): Int {
|
||||
return when(type) {
|
||||
RevocationStateType.notRevoked ->
|
||||
if (other.isNotRevoked()) 0
|
||||
else -1
|
||||
RevocationStateType.softRevoked ->
|
||||
if (other.isNotRevoked()) 1
|
||||
// Compare soft dates in reverse
|
||||
else if (other.isSoftRevocation()) other.date.compareTo(date)
|
||||
else -1
|
||||
RevocationStateType.hardRevoked ->
|
||||
if (other.isHardRevocation()) 0
|
||||
else 1
|
||||
else -> throw AssertionError("Unknown type: $type")
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return buildString {
|
||||
append(type)
|
||||
if (isSoftRevocation()) append(" (${DateUtil.formatUTCDate(date)})")
|
||||
}
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return type.hashCode() * 31 + if (isSoftRevocation()) date.hashCode() else 0
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (other == null) {
|
||||
return false
|
||||
}
|
||||
if (this === other) {
|
||||
return true
|
||||
}
|
||||
if (other !is RevocationState) {
|
||||
return false
|
||||
}
|
||||
if (type != other.type) {
|
||||
return false
|
||||
}
|
||||
if (isSoftRevocation()) {
|
||||
return DateUtil.toSecondsPrecision(date).time == DateUtil.toSecondsPrecision(other.date).time
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.algorithm
|
||||
|
||||
enum class RevocationStateType {
|
||||
/**
|
||||
* Certificate is not revoked.
|
||||
*/
|
||||
notRevoked,
|
||||
|
||||
/**
|
||||
* Certificate is revoked with a soft revocation.
|
||||
*/
|
||||
softRevoked,
|
||||
|
||||
/**
|
||||
* Certificate is revoked with a hard revocation.
|
||||
*/
|
||||
hardRevoked
|
||||
}
|
||||
|
|
@ -0,0 +1,401 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.algorithm
|
||||
|
||||
import org.bouncycastle.bcpg.SignatureSubpacketTags.*
|
||||
|
||||
/**
|
||||
* Enumeration of possible subpackets that might be found in the hashed and unhashed area of an OpenPGP signature.
|
||||
*
|
||||
* See [RFC4880: Signature Subpacket Specification](https://tools.ietf.org/html/rfc4880#section-5.2.3.1)
|
||||
*/
|
||||
enum class SignatureSubpacket(val code: Int) {
|
||||
/**
|
||||
* The time the signature was made.
|
||||
* MUST be present in the hashed area of the signature.
|
||||
*
|
||||
* See [Signature Creation Time](https://tools.ietf.org/html/rfc4880#section-5.2.3.4)
|
||||
*/
|
||||
signatureCreationTime(2),
|
||||
|
||||
/**
|
||||
* The validity period of the signature. This is the number of seconds
|
||||
* after the signature creation time that the signature expires. If
|
||||
* this is not present or has a value of zero, it never expires.
|
||||
*
|
||||
* See [Signature Expiration Time](https://tools.ietf.org/html/rfc4880#section-5.2.3.10)
|
||||
*/
|
||||
signatureExpirationTime(3),
|
||||
|
||||
/**
|
||||
* Denotes whether the signature is exportable for other users.
|
||||
*
|
||||
* See [Exportable Certification](https://tools.ietf.org/html/rfc4880#section-5.2.3.11)
|
||||
*/
|
||||
exportableCertification(4),
|
||||
|
||||
/**
|
||||
* Signer asserts that the key is not only valid but also trustworthy at
|
||||
* the specified level. Level 0 has the same meaning as an ordinary
|
||||
* validity signature. Level 1 means that the signed key is asserted to
|
||||
* be a valid, trusted introducer, with the 2nd octet of the body
|
||||
* specifying the degree of trust. Level 2 means that the signed key is
|
||||
* asserted to be trusted to issue level 1 trust signatures, i.e., that
|
||||
* it is a "meta introducer". Generally, a level n trust signature
|
||||
* asserts that a key is trusted to issue level n-1 trust signatures.
|
||||
* The trust amount is in a range from 0-255, interpreted such that
|
||||
* values less than 120 indicate partial trust and values of 120 or
|
||||
* greater indicate complete trust. Implementations SHOULD emit values
|
||||
* of 60 for partial trust and 120 for complete trust.
|
||||
*
|
||||
* See [Trust Signature](https://tools.ietf.org/html/rfc4880#section-5.2.3.13)
|
||||
*/
|
||||
trustSignature(5),
|
||||
|
||||
/**
|
||||
* Used in conjunction with trust Signature packets (of level greater 0) to
|
||||
* limit the scope of trust that is extended. Only signatures by the
|
||||
* target key on User IDs that match the regular expression in the body
|
||||
* of this packet have trust extended by the trust Signature subpacket.
|
||||
* The regular expression uses the same syntax as the Henry Spencer's
|
||||
* "almost public domain" regular expression [REGEX] package. A
|
||||
* description of the syntax is found in Section 8 below.
|
||||
*
|
||||
* See [Regular Expression](https://tools.ietf.org/html/rfc4880#section-5.2.3.14)
|
||||
*/
|
||||
regularExpression(6),
|
||||
|
||||
/**
|
||||
* Signature's revocability status. The packet body contains a Boolean
|
||||
* flag indicating whether the signature is revocable. Signatures that
|
||||
* are not revocable have any later revocation signatures ignored. They
|
||||
* represent a commitment by the signer that he cannot revoke his
|
||||
* signature for the life of his key. If this packet is not present,
|
||||
* the signature is revocable.
|
||||
*
|
||||
* See [Revocable](https://tools.ietf.org/html/rfc4880#section-5.2.3.12)
|
||||
*/
|
||||
revocable(7),
|
||||
|
||||
/**
|
||||
* The validity period of the key. This is the number of seconds after
|
||||
* the key creation time that the key expires. If this is not present
|
||||
* or has a value of zero, the key never expires. This is found only on
|
||||
* a self-signature.
|
||||
*
|
||||
* See [Key Expiration Time](https://tools.ietf.org/html/rfc4880#section-5.2.3.6)
|
||||
*/
|
||||
keyExpirationTime(9),
|
||||
|
||||
/**
|
||||
* Placeholder for backwards compatibility.
|
||||
*/
|
||||
placeholder(10),
|
||||
|
||||
/**
|
||||
* Symmetric algorithm numbers that indicate which algorithms the keyholder
|
||||
* prefers to use. The subpackets body is an ordered list of
|
||||
* octets with the most preferred listed first. It is assumed that only
|
||||
* algorithms listed are supported by the recipient's software.
|
||||
* This is only found on a self-signature.
|
||||
*
|
||||
* See [Preferred Symmetric Algorithms](https://tools.ietf.org/html/rfc4880#section-5.2.3.7)
|
||||
*/
|
||||
preferredSymmetricAlgorithms(11),
|
||||
|
||||
/**
|
||||
* Authorizes the specified key to issue revocation signatures for this
|
||||
* key. Class octet must have bit 0x80 set. If the bit 0x40 is set,
|
||||
* then this means that the revocation information is sensitive. Other
|
||||
* bits are for future expansion to other kinds of authorizations. This
|
||||
* is found on a self-signature.
|
||||
*
|
||||
* If the "sensitive" flag is set, the keyholder feels this subpacket
|
||||
* contains private trust information that describes a real-world
|
||||
* sensitive relationship. If this flag is set, implementations SHOULD
|
||||
* NOT export this signature to other users except in cases where the
|
||||
* data needs to be available: when the signature is being sent to the
|
||||
* designated revoker, or when it is accompanied by a revocation
|
||||
* signature from that revoker. Note that it may be appropriate to
|
||||
* isolate this subpacket within a separate signature so that it is not
|
||||
* combined with other subpackets that need to be exported.
|
||||
*
|
||||
* See [Revocation Key](https://tools.ietf.org/html/rfc4880#section-5.2.3.15)
|
||||
*/
|
||||
revocationKey(12),
|
||||
|
||||
/**
|
||||
* The OpenPGP Key ID of the key issuing the signature.
|
||||
*
|
||||
* See [Issuer Key ID](https://tools.ietf.org/html/rfc4880#section-5.2.3.5)
|
||||
*/
|
||||
issuerKeyId(16),
|
||||
|
||||
/**
|
||||
* This subpacket describes a "notation" on the signature that the
|
||||
* issuer wishes to make. The notation has a name and a value, each of
|
||||
* which are strings of octets. There may be more than one notation in
|
||||
* a signature. Notations can be used for any extension the issuer of
|
||||
* the signature cares to make. The "flags" field holds four octets of
|
||||
* flags.
|
||||
*
|
||||
* See [Notation Data](https://tools.ietf.org/html/rfc4880#section-5.2.3.16)
|
||||
*/
|
||||
notationData(20),
|
||||
|
||||
/**
|
||||
* Message digest algorithm numbers that indicate which algorithms the
|
||||
* keyholder prefers to receive. Like the preferred symmetric
|
||||
* algorithms, the list is ordered.
|
||||
* This is only found on a self-signature.
|
||||
*
|
||||
* See [Preferred Hash Algorithms](https://tools.ietf.org/html/rfc4880#section-5.2.3.8)
|
||||
*/
|
||||
preferredHashAlgorithms(21),
|
||||
|
||||
/**
|
||||
* Compression algorithm numbers that indicate which algorithms the
|
||||
* keyholder prefers to use. Like the preferred symmetric algorithms, the
|
||||
* list is ordered. If this subpacket is not included, ZIP is preferred.
|
||||
* A zero denotes that uncompressed data is preferred; the keyholder's
|
||||
* software might have no compression software in that implementation.
|
||||
* This is only found on a self-signature.
|
||||
*
|
||||
* See [Preferred Compressio Algorithms](https://tools.ietf.org/html/rfc4880#section-5.2.3.9)
|
||||
*/
|
||||
preferredCompressionAlgorithms(22),
|
||||
|
||||
/**
|
||||
* This is a list of one-bit flags that indicate preferences that the
|
||||
* keyholder has about how the key is handled on a key server. All
|
||||
* undefined flags MUST be zero.
|
||||
* This is found only on a self-signature.
|
||||
*
|
||||
* See [Key Server Preferences](https://tools.ietf.org/html/rfc4880#section-5.2.3.17)
|
||||
*/
|
||||
keyServerPreferences(23),
|
||||
|
||||
/**
|
||||
* This is a URI of a key server that the keyholder prefers be used for
|
||||
* updates. Note that keys with multiple User IDs can have a preferred
|
||||
* key server for each User ID. Note also that since this is a URI, the
|
||||
* key server can actually be a copy of the key retrieved by ftp, http,
|
||||
* finger, etc.
|
||||
*
|
||||
* See [Preferred Key Server](https://tools.ietf.org/html/rfc4880#section-5.2.3.18)
|
||||
*/
|
||||
preferredKeyServers(24),
|
||||
|
||||
/**
|
||||
* This is a flag in a User ID's self-signature that states whether this
|
||||
* User ID is the main User ID for this key. It is reasonable for an
|
||||
* implementation to resolve ambiguities in preferences, etc. by
|
||||
* referring to the primary User ID. If this flag is absent, its value
|
||||
* is zero. If more than one User ID in a key is marked as primary, the
|
||||
* implementation may resolve the ambiguity in any way it sees fit, but
|
||||
* it is RECOMMENDED that priority be given to the User ID with the most
|
||||
* recent self-signature.
|
||||
*
|
||||
* When appearing on a self-signature on a User ID packet, this
|
||||
* subpacket applies only to User ID packets. When appearing on a
|
||||
* self-signature on a User Attribute packet, this subpacket applies
|
||||
* only to User Attribute packets. That is to say, there are two
|
||||
* different and independent "primaries" -- one for User IDs, and one
|
||||
* for User Attributes.
|
||||
*
|
||||
* See [Primary User-ID](https://tools.ietf.org/html/rfc4880#section-5.2.3.19)
|
||||
*/
|
||||
primaryUserId(25),
|
||||
|
||||
/**
|
||||
* This subpacket contains a URI of a document that describes the policy
|
||||
* under which the signature was issued.
|
||||
*
|
||||
* See [Policy URL](https://tools.ietf.org/html/rfc4880#section-5.2.3.20)
|
||||
*/
|
||||
policyUrl(26),
|
||||
|
||||
/**
|
||||
* This subpacket contains a list of binary flags that hold information
|
||||
* about a key. It is a string of octets, and an implementation MUST
|
||||
* NOT assume a fixed size. This is so it can grow over time. If a
|
||||
* list is shorter than an implementation expects, the unstated flags
|
||||
* are considered to be zero.
|
||||
*
|
||||
* See [Key Flags](https://tools.ietf.org/html/rfc4880#section-5.2.3.21)
|
||||
*/
|
||||
keyFlags(27),
|
||||
|
||||
/**
|
||||
* This subpacket allows a keyholder to state which User ID is
|
||||
* responsible for the signing. Many keyholders use a single key for
|
||||
* different purposes, such as business communications as well as
|
||||
* personal communications. This subpacket allows such a keyholder to
|
||||
* state which of their roles is making a signature.
|
||||
*
|
||||
* See [Signer's User ID](https://tools.ietf.org/html/rfc4880#section-5.2.3.22)
|
||||
*/
|
||||
signerUserId(28),
|
||||
|
||||
/**
|
||||
* This subpacket is used only in key revocation and certification
|
||||
* revocation signatures. It describes the reason why the key or
|
||||
* certificate was revoked.
|
||||
*
|
||||
* The first octet contains a machine-readable code that denotes the
|
||||
* reason for the revocation:
|
||||
*
|
||||
* 0 - No reason specified (key revocations or cert revocations)
|
||||
* 1 - Key is superseded (key revocations)
|
||||
* 2 - Key material has been compromised (key revocations)
|
||||
* 3 - Key is retired and no longer used (key revocations)
|
||||
* 32 - User ID information is no longer valid (cert revocations)
|
||||
* 100-110 - Private Use
|
||||
*
|
||||
* See [Reason for Revocation](https://tools.ietf.org/html/rfc4880#section-5.2.3.23)
|
||||
*/
|
||||
revocationReason(29),
|
||||
|
||||
/**
|
||||
* The Features subpacket denotes which advanced OpenPGP features a
|
||||
* user's implementation supports. This is so that as features are
|
||||
* added to OpenPGP that cannot be backwards-compatible, a user can
|
||||
* state that they can use that feature. The flags are single bits that
|
||||
* indicate that a given feature is supported.
|
||||
*
|
||||
* This subpacket is similar to a preferences subpacket, and only
|
||||
* appears in a self-signature.
|
||||
*
|
||||
* See [Features](https://tools.ietf.org/html/rfc4880#section-5.2.3.24)
|
||||
*/
|
||||
features(30),
|
||||
|
||||
/**
|
||||
* This subpacket identifies a specific target signature to which a
|
||||
* signature refers. For revocation signatures, this subpacket
|
||||
* provides explicit designation of which signature is being revoked.
|
||||
* For a third-party or timestamp signature, this designates what
|
||||
* signature is signed. All arguments are an identifier of that target
|
||||
* signature.
|
||||
*
|
||||
* The N octets of hash data MUST be the size of the hash of the
|
||||
* signature. For example, a target signature with a SHA-1 hash MUST
|
||||
* have 20 octets of hash data.
|
||||
*
|
||||
* See [Signature Target](https://tools.ietf.org/html/rfc4880#section-5.2.3.25)
|
||||
*/
|
||||
signatureTarget(31),
|
||||
|
||||
/**
|
||||
* This subpacket contains a complete Signature packet body as
|
||||
* specified in Section 5.2 above. It is useful when one signature
|
||||
* needs to refer to, or be incorporated in, another signature.
|
||||
*
|
||||
* See [Embedded Signature](https://tools.ietf.org/html/rfc4880#section-5.2.3.26)
|
||||
*/
|
||||
embeddedSignature(32),
|
||||
|
||||
/**
|
||||
* The OpenPGP Key fingerprint of the key issuing the signature. This
|
||||
* subpacket SHOULD be included in all signatures. If the version of
|
||||
* the issuing key is 4 and an Issuer subpacket is also included in the
|
||||
* signature, the key ID of the Issuer subpacket MUST match the low 64
|
||||
* bits of the fingerprint.
|
||||
*
|
||||
* Note that the length N of the fingerprint for a version 4 key is 20
|
||||
* octets; for a version 5 key N is 32.
|
||||
*
|
||||
* See [Issuer Fingerprint](https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10#section-5.2.3.28)
|
||||
*/
|
||||
issuerFingerprint(33),
|
||||
|
||||
/**
|
||||
* AEAD algorithm numbers that indicate which AEAD algorithms the
|
||||
* keyholder prefers to use. The subpackets body is an ordered list of
|
||||
* octets with the most preferred listed first. It is assumed that only
|
||||
* algorithms listed are supported by the recipient's software.
|
||||
* This is only found on a self-signature.
|
||||
* Note that support for the AEAD Encrypted Data packet in the general
|
||||
* is indicated by a Feature Flag.
|
||||
*
|
||||
* See [Preferred AEAD Algorithms](https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10#section-5.2.3.8)
|
||||
*/
|
||||
preferredAEADAlgorithms(39),
|
||||
|
||||
/**
|
||||
* The OpenPGP Key fingerprint of the intended recipient primary key.
|
||||
* If one or more subpackets of this type are included in a signature,
|
||||
* it SHOULD be considered valid only in an encrypted context, where the
|
||||
* key it was encrypted to is one of the indicated primary keys, or one
|
||||
* of their subkeys. This can be used to prevent forwarding a signature
|
||||
* outside its intended, encrypted context.
|
||||
*
|
||||
* Note that the length N of the fingerprint for a version 4 key is 20
|
||||
* octets; for a version 5 key N is 32.
|
||||
*
|
||||
* See [Intended Recipient Fingerprint](https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10#section-5.2.3.29)
|
||||
*/
|
||||
intendedRecipientFingerprint(35),
|
||||
|
||||
/**
|
||||
* This subpacket MUST only appear as a hashed subpacket of an
|
||||
* Attestation Key Signature. It has no meaning in any other signature
|
||||
* type. It is used by the primary key to attest to a set of third-
|
||||
* party certifications over the associated User ID or User Attribute.
|
||||
* This enables the holder of an OpenPGP primary key to mark specific
|
||||
* third-party certifications as re-distributable with the rest of the
|
||||
* Transferable Public Key (see the "No-modify" flag in "Key Server
|
||||
* Preferences", above). Implementations MUST include exactly one
|
||||
* Attested Certification subpacket in any generated Attestation Key
|
||||
* Signature.
|
||||
*
|
||||
* See [Attested Certification](https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10#section-5.2.3.30)
|
||||
*/
|
||||
attestedCertification(37)
|
||||
;
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Return the [SignatureSubpacket] that corresponds to the provided id.
|
||||
* If an unmatched code is presented, return null.
|
||||
*
|
||||
* @param code id
|
||||
* @return signature subpacket
|
||||
*/
|
||||
@JvmStatic
|
||||
fun fromCode(code: Int): SignatureSubpacket? {
|
||||
return values().firstOrNull {
|
||||
it.code == code
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the [SignatureSubpacket] that corresponds to the provided code.
|
||||
*
|
||||
* @param code code
|
||||
* @return signature subpacket
|
||||
* @throws NoSuchElementException in case of an unmatched subpacket tag
|
||||
*/
|
||||
@JvmStatic
|
||||
fun requireFromCode(code: Int): SignatureSubpacket {
|
||||
return fromCode(code) ?:
|
||||
throw NoSuchElementException("No SignatureSubpacket tag found with code $code")
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an array of signature subpacket tags into a list of [SignatureSubpacket SignatureSubpackets].
|
||||
*
|
||||
* @param codes array of codes
|
||||
* @return list of subpackets
|
||||
*/
|
||||
@JvmStatic
|
||||
fun fromCodes(vararg codes: Int): List<SignatureSubpacket> {
|
||||
return codes.toList().mapNotNull {
|
||||
fromCode(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,228 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.algorithm
|
||||
|
||||
import org.bouncycastle.openpgp.PGPSignature
|
||||
|
||||
/**
|
||||
* Enum that enlists all the Signature Types defined in rfc4880 section 5.2.1
|
||||
* See [PGPSignature] for comparison.
|
||||
*
|
||||
* See [rfc4880 §5.2.1. Signature Types](https://tools.ietf.org/html/rfc4880#section-5.11)
|
||||
*/
|
||||
enum class SignatureType(val code: Int) {
|
||||
|
||||
/**
|
||||
* Signature of a binary document.
|
||||
* This means the signer owns it, created it, or certifies that it
|
||||
* has not been modified.
|
||||
*/
|
||||
BINARY_DOCUMENT(0x00),
|
||||
|
||||
/**
|
||||
* Signature of a canonical text document.
|
||||
* This means the signer owns it, created it, or certifies that it
|
||||
* has not been modified. The signature is calculated over the text
|
||||
* data with its line endings converted to {@code <CR><LF>}.
|
||||
*/
|
||||
CANONICAL_TEXT_DOCUMENT(0x01),
|
||||
|
||||
/**
|
||||
* Standalone signature.
|
||||
* This signature is a signature of only its own subpacket contents.
|
||||
* It is calculated identically to a signature over a zero-length
|
||||
* binary document. Note that it doesn't make sense to have a V3
|
||||
* standalone signature.
|
||||
*/
|
||||
STANDALONE(0x02),
|
||||
|
||||
/**
|
||||
* Generic certification of a User ID and Public-Key packet.
|
||||
* 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_CERTIFICATION(0x10),
|
||||
|
||||
/**
|
||||
* Persona certification of a User ID and Public-Key packet.
|
||||
* The issuer of this certification has not done any verification of
|
||||
* the claim that the owner of this key is the User ID specified.
|
||||
*/
|
||||
NO_CERTIFICATION(0x11),
|
||||
|
||||
/**
|
||||
* Casual certification of a User ID and Public-Key packet.
|
||||
* The issuer of this certification has done some casual
|
||||
* verification of the claim of identity.
|
||||
*/
|
||||
CASUAL_CERTIFICATION(0x12),
|
||||
|
||||
/**
|
||||
* Positive certification of a User ID and Public-Key packet.
|
||||
* The issuer of this certification has done substantial
|
||||
* verification of the claim of identity.
|
||||
*/
|
||||
POSITIVE_CERTIFICATION(0x13),
|
||||
|
||||
/**
|
||||
* Subkey Binding Signature.
|
||||
* This signature is a statement by the top-level signing key that
|
||||
* indicates that it owns the subkey. This signature is calculated
|
||||
* directly on the primary key and subkey, and not on any User ID or
|
||||
* other packets. A signature that binds a signing subkey MUST have
|
||||
* an Embedded Signature subpacket in this binding signature that
|
||||
* contains a [#PRIMARYKEY_BINDING] signature made by the
|
||||
* signing subkey on the primary key and subkey.
|
||||
*/
|
||||
SUBKEY_BINDING(0x18),
|
||||
|
||||
/**
|
||||
* Primary Key Binding Signature
|
||||
* This signature is a statement by a signing subkey, indicating
|
||||
* that it is owned by the primary key and subkey. This signature
|
||||
* is calculated the same way as a [#SUBKEY_BINDING] signature:
|
||||
* directly on the primary key and subkey, and not on any User ID or
|
||||
* other packets.
|
||||
*/
|
||||
PRIMARYKEY_BINDING(0x19),
|
||||
|
||||
/**
|
||||
* Signature directly on a key
|
||||
* This signature is calculated directly on a key. It binds the
|
||||
* information in the Signature subpackets to the key, and is
|
||||
* appropriate to be used for subpackets that provide information
|
||||
* about the key, such as the Revocation Key subpacket. It is also
|
||||
* appropriate for statements that non-self certifiers want to make
|
||||
* about the key itself, rather than the binding between a key and a
|
||||
* name.
|
||||
*/
|
||||
DIRECT_KEY(0x1f),
|
||||
|
||||
/**
|
||||
* Key revocation signature
|
||||
* The signature is calculated directly on the key being revoked. A
|
||||
* revoked key is not to be used. Only revocation signatures by the
|
||||
* key being revoked, or by an authorized revocation key, should be
|
||||
* considered valid revocation signatures.
|
||||
*/
|
||||
KEY_REVOCATION(0x20),
|
||||
|
||||
/**
|
||||
* Subkey revocation signature
|
||||
* The signature is calculated directly on the subkey being revoked.
|
||||
* A revoked subkey is not to be used. Only revocation signatures
|
||||
* by the top-level signature key that is bound to this subkey, or
|
||||
* by an authorized revocation key, should be considered valid
|
||||
* revocation signatures.
|
||||
*/
|
||||
SUBKEY_REVOCATION(0x28),
|
||||
|
||||
/**
|
||||
* Certification revocation signature
|
||||
* This signature revokes an earlier User ID certification signature
|
||||
* (signature class 0x10 through 0x13) or signature [#DIRECT_KEY].
|
||||
* It should be issued by the same key that issued the
|
||||
* revoked signature or an authorized revocation key. The signature
|
||||
* is computed over the same data as the certificate that it
|
||||
* revokes, and should have a later creation date than that
|
||||
* certificate.
|
||||
*/
|
||||
CERTIFICATION_REVOCATION(0x30),
|
||||
|
||||
/**
|
||||
* Timestamp signature.
|
||||
* This signature is only meaningful for the timestamp contained in
|
||||
* it.
|
||||
*/
|
||||
TIMESTAMP(0x40),
|
||||
|
||||
/**
|
||||
* Third-Party Confirmation signature.
|
||||
* This signature is a signature over some other OpenPGP Signature
|
||||
* packet(s). It is analogous to a notary seal on the signed data.
|
||||
* A third-party signature SHOULD include Signature Target
|
||||
* subpacket(s) to give easy identification. Note that we really do
|
||||
* mean SHOULD. There are plausible uses for this (such as a blind
|
||||
* party that only sees the signature, not the key or source
|
||||
* document) that cannot include a target subpacket.
|
||||
*/
|
||||
THIRD_PARTY_CONFIRMATION(0x50)
|
||||
;
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Convert a numerical id into a [SignatureType].
|
||||
*
|
||||
* @param code numeric id
|
||||
* @return signature type enum
|
||||
*/
|
||||
@JvmStatic
|
||||
fun fromCode(code: Int): SignatureType? {
|
||||
return values().firstOrNull {
|
||||
it.code == code
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a numerical id into a [SignatureType].
|
||||
*
|
||||
* @param code numeric id
|
||||
* @return signature type enum
|
||||
* @throws NoSuchElementException in case of an unmatched signature type code
|
||||
*/
|
||||
@JvmStatic
|
||||
fun requireFromCode(code: Int): SignatureType {
|
||||
return fromCode(code) ?:
|
||||
throw NoSuchElementException("Signature type 0x${Integer.toHexString(code)} appears to be invalid.")
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a numerical id into a [SignatureType].
|
||||
*
|
||||
* @param code numeric id
|
||||
* @return signature type enum
|
||||
* @throws IllegalArgumentException in case of an unmatched signature type code
|
||||
*/
|
||||
@JvmStatic
|
||||
@Deprecated("Deprecated in favor of requireFromCode",
|
||||
ReplaceWith("requireFromCode"))
|
||||
fun valueOf(code: Int): SignatureType {
|
||||
try {
|
||||
return requireFromCode(code)
|
||||
} catch (e: NoSuchElementException) {
|
||||
throw IllegalArgumentException(e.message)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun isRevocationSignature(signatureType: Int): Boolean {
|
||||
return isRevocationSignature(valueOf(signatureType))
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun isRevocationSignature(signatureType: SignatureType): Boolean {
|
||||
return when (signatureType) {
|
||||
BINARY_DOCUMENT,
|
||||
CANONICAL_TEXT_DOCUMENT,
|
||||
STANDALONE,
|
||||
GENERIC_CERTIFICATION,
|
||||
NO_CERTIFICATION,
|
||||
CASUAL_CERTIFICATION,
|
||||
POSITIVE_CERTIFICATION,
|
||||
SUBKEY_BINDING,
|
||||
PRIMARYKEY_BINDING,
|
||||
DIRECT_KEY,
|
||||
TIMESTAMP,
|
||||
THIRD_PARTY_CONFIRMATION -> false
|
||||
KEY_REVOCATION,
|
||||
SUBKEY_REVOCATION,
|
||||
CERTIFICATION_REVOCATION -> true
|
||||
else -> throw IllegalArgumentException("Unknown signature type: $signatureType")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.algorithm
|
||||
|
||||
/**
|
||||
* Enumeration of possible encoding formats of the content of the literal data packet.
|
||||
*
|
||||
* See [RFC4880: Literal Data Packet](https://tools.ietf.org/html/rfc4880#section-5.9)
|
||||
*/
|
||||
enum class StreamEncoding(val code: Char) {
|
||||
|
||||
/**
|
||||
* The Literal packet contains binary data.
|
||||
*/
|
||||
BINARY('b'),
|
||||
|
||||
/**
|
||||
* The Literal packet contains text data, and thus may need line ends converted to local form, or other
|
||||
* text-mode changes.
|
||||
*/
|
||||
TEXT('t'),
|
||||
|
||||
/**
|
||||
* Indication that the implementation believes that the literal data contains UTF-8 text.
|
||||
*/
|
||||
UTF8('u'),
|
||||
|
||||
/**
|
||||
* Early versions of PGP also defined a value of 'l' as a 'local' mode for machine-local conversions.
|
||||
* RFC 1991 [RFC1991] incorrectly stated this local mode flag as '1' (ASCII numeral one).
|
||||
* Both of these local modes are deprecated.
|
||||
*/
|
||||
@Deprecated("LOCAL is deprecated.")
|
||||
LOCAL('l'),
|
||||
;
|
||||
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Return the [StreamEncoding] corresponding to the provided code identifier.
|
||||
* If no matching encoding is found, return null.
|
||||
*
|
||||
* @param code identifier
|
||||
* @return encoding enum
|
||||
*/
|
||||
@JvmStatic
|
||||
fun fromCode(code: Int): StreamEncoding? {
|
||||
return values().firstOrNull {
|
||||
it.code == code.toChar()
|
||||
} ?: if (code == 1) return LOCAL else null
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the [StreamEncoding] corresponding to the provided code identifier.
|
||||
* If no matching encoding is found, throw a [NoSuchElementException].
|
||||
*
|
||||
* @param code identifier
|
||||
* @return encoding enum
|
||||
*
|
||||
* @throws NoSuchElementException in case of an unmatched identifier
|
||||
*/
|
||||
@JvmStatic
|
||||
fun requireFromCode(code: Int): StreamEncoding {
|
||||
return fromCode(code) ?:
|
||||
throw NoSuchElementException("No StreamEncoding found for code $code")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.algorithm
|
||||
|
||||
/**
|
||||
* Enumeration of possible symmetric encryption algorithms.
|
||||
*
|
||||
* See [RFC4880: Symmetric-Key Algorithms](https://tools.ietf.org/html/rfc4880#section-9.2)
|
||||
*/
|
||||
enum class SymmetricKeyAlgorithm(val algorithmId: Int) {
|
||||
|
||||
/**
|
||||
* Plaintext or unencrypted data.
|
||||
*/
|
||||
NULL (0),
|
||||
|
||||
/**
|
||||
* IDEA is deprecated.
|
||||
* @deprecated use a different algorithm.
|
||||
*/
|
||||
@Deprecated("IDEA is deprecated.")
|
||||
IDEA (1),
|
||||
|
||||
/**
|
||||
* TripleDES (DES-EDE - 168 bit key derived from 192).
|
||||
*/
|
||||
TRIPLE_DES (2),
|
||||
|
||||
/**
|
||||
* CAST5 (128-bit key, as per RFC2144).
|
||||
*/
|
||||
CAST5 (3),
|
||||
|
||||
/**
|
||||
* Blowfish (128-bit key, 16 rounds).
|
||||
*/
|
||||
BLOWFISH (4),
|
||||
|
||||
/**
|
||||
* Reserved in RFC4880.
|
||||
* SAFER-SK128 (13 rounds)
|
||||
*/
|
||||
SAFER (5),
|
||||
|
||||
/**
|
||||
* Reserved in RFC4880.
|
||||
* Reserved for DES/SK
|
||||
*/
|
||||
DES (6),
|
||||
|
||||
/**
|
||||
* AES with 128-bit key.
|
||||
*/
|
||||
AES_128 (7),
|
||||
|
||||
/**
|
||||
* AES with 192-bit key.
|
||||
*/
|
||||
AES_192 (8),
|
||||
|
||||
/**
|
||||
* AES with 256-bit key.
|
||||
*/
|
||||
AES_256 (9),
|
||||
|
||||
/**
|
||||
* Twofish with 256-bit key.
|
||||
*/
|
||||
TWOFISH (10),
|
||||
|
||||
/**
|
||||
* Reserved for Camellia with 128-bit key.
|
||||
*/
|
||||
CAMELLIA_128 (11),
|
||||
|
||||
/**
|
||||
* Reserved for Camellia with 192-bit key.
|
||||
*/
|
||||
CAMELLIA_192 (12),
|
||||
|
||||
/**
|
||||
* Reserved for Camellia with 256-bit key.
|
||||
*/
|
||||
CAMELLIA_256 (13),
|
||||
;
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Return the [SymmetricKeyAlgorithm] enum that corresponds to the provided numeric id.
|
||||
* If an invalid id is provided, null is returned.
|
||||
*
|
||||
* @param id numeric algorithm id
|
||||
* @return symmetric key algorithm enum
|
||||
*/
|
||||
@JvmStatic
|
||||
fun fromId(id: Int): SymmetricKeyAlgorithm? {
|
||||
return values().firstOrNull {
|
||||
it.algorithmId == id
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the [SymmetricKeyAlgorithm] enum that corresponds to the provided numeric id.
|
||||
* If an invalid id is provided, throw a [NoSuchElementException].
|
||||
*
|
||||
* @param id numeric algorithm id
|
||||
* @return symmetric key algorithm enum
|
||||
*
|
||||
* @throws NoSuchElementException if an unmatched id is provided
|
||||
*/
|
||||
@JvmStatic
|
||||
fun requireFromId(id: Int): SymmetricKeyAlgorithm {
|
||||
return fromId(id) ?:
|
||||
throw NoSuchElementException("No SymmetricKeyAlgorithm found for id $id")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.algorithm
|
||||
|
||||
/**
|
||||
* Facade class for [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.
|
||||
*/
|
||||
class Trustworthiness(amount: Int, depth: Int) {
|
||||
val depth = capDepth(depth)
|
||||
val amount = capAmount(amount)
|
||||
|
||||
/**
|
||||
* Returns true, if the trust amount is equal to 0.
|
||||
* This means the key is not trusted.
|
||||
*
|
||||
* Otherwise return false
|
||||
* @return true if untrusted
|
||||
*/
|
||||
fun isNotTrusted() = amount == 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
|
||||
*/
|
||||
fun isMarginallyTrusted() = amount > 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
|
||||
*/
|
||||
fun isFullyTrusted() = amount >= 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
|
||||
*/
|
||||
fun isIntroducer() = depth >= 1
|
||||
|
||||
/**
|
||||
* Return true, if the certified cert can introduce certificates with trust depth of <pre>otherDepth</pre>.
|
||||
*
|
||||
* @param otherDepth other certifications trust depth
|
||||
* @return true if the cert can introduce the other
|
||||
*/
|
||||
fun canIntroduce(otherDepth: Int) = depth > otherDepth
|
||||
|
||||
/**
|
||||
* Return true, if the certified cert can introduce certificates with the given <pre>other</pre> trust depth.
|
||||
*
|
||||
* @param other other certificates trust depth
|
||||
* @return true if the cert can introduce the other
|
||||
*/
|
||||
fun canIntroduce(other: Trustworthiness) = canIntroduce(other.depth)
|
||||
|
||||
companion object {
|
||||
|
||||
const val THRESHOLD_FULLY_CONVINCED = 120 // greater or equal is fully trusted
|
||||
const val MARGINALLY_CONVINCED = 60 // default value for marginally convinced
|
||||
const val NOT_TRUSTED = 0 // 0 is not trusted
|
||||
|
||||
/**
|
||||
* This means that we are fully convinced of the trustworthiness of the key.
|
||||
*
|
||||
* @return builder
|
||||
*/
|
||||
@JvmStatic
|
||||
fun fullyTrusted() = Builder(THRESHOLD_FULLY_CONVINCED)
|
||||
|
||||
/**
|
||||
* This means that we are marginally (partially) convinced of the trustworthiness of the key.
|
||||
*
|
||||
* @return builder
|
||||
*/
|
||||
@JvmStatic
|
||||
fun marginallyTrusted() = Builder(MARGINALLY_CONVINCED)
|
||||
|
||||
/**
|
||||
* This means that we do not trust the key.
|
||||
* Can be used to overwrite previous trust.
|
||||
*
|
||||
* @return builder
|
||||
*/
|
||||
@JvmStatic
|
||||
fun untrusted() = Builder(NOT_TRUSTED)
|
||||
|
||||
@JvmStatic
|
||||
private fun capAmount(amount: Int): Int {
|
||||
if (amount !in 0..255) {
|
||||
throw IllegalArgumentException("Trust amount MUST be a value between 0 and 255")
|
||||
}
|
||||
return amount
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
private fun capDepth(depth: Int): Int {
|
||||
if (depth !in 0..255) {
|
||||
throw IllegalArgumentException("Trust depth MUST be a value between 0 and 255")
|
||||
}
|
||||
return depth
|
||||
}
|
||||
}
|
||||
|
||||
class Builder(val amount: Int) {
|
||||
|
||||
/**
|
||||
* The key is a trusted introducer (depth 1).
|
||||
* Certifications made by this key are considered trustworthy.
|
||||
*
|
||||
* @return trust
|
||||
*/
|
||||
fun introducer() = Trustworthiness(amount, 1)
|
||||
|
||||
/**
|
||||
* The key is a meta introducer (depth 2).
|
||||
* This key can introduce trusted introducers of depth 1.
|
||||
*
|
||||
* @return trust
|
||||
*/
|
||||
fun metaIntroducer() = Trustworthiness(amount, 2)
|
||||
|
||||
/**
|
||||
* The key is a meta introducer of depth <pre>n</pre>.
|
||||
* This key can introduce meta introducers of depth <pre>n - 1</pre>.
|
||||
*
|
||||
* @param n depth
|
||||
* @return trust
|
||||
*/
|
||||
fun metaIntroducerOfDepth(d: Int) = Trustworthiness(amount, d)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.algorithm.negotiation
|
||||
|
||||
import org.pgpainless.algorithm.HashAlgorithm
|
||||
import org.pgpainless.policy.Policy
|
||||
|
||||
/**
|
||||
* Interface for a class that negotiates [HashAlgorithms][HashAlgorithm].
|
||||
*
|
||||
* You can provide your own implementation using custom logic by implementing the
|
||||
* [negotiateHashAlgorithm(Set)] method.
|
||||
*/
|
||||
interface HashAlgorithmNegotiator {
|
||||
|
||||
/**
|
||||
* Pick one [HashAlgorithm] from the ordered set of acceptable algorithms.
|
||||
*
|
||||
* @param orderedPrefs hash algorithm preferences
|
||||
* @return picked algorithms
|
||||
*/
|
||||
fun negotiateHashAlgorithm(orderedPrefs: Set<HashAlgorithm>): HashAlgorithm
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Return an instance that negotiates [HashAlgorithms][HashAlgorithm] used for non-revocation signatures
|
||||
* based on the given [Policy].
|
||||
*
|
||||
* @param policy algorithm policy
|
||||
* @return negotiator
|
||||
*/
|
||||
@JvmStatic
|
||||
fun negotiateSignatureHashAlgorithm(policy: Policy): HashAlgorithmNegotiator {
|
||||
return negotiateByPolicy(policy.signatureHashAlgorithmPolicy)
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an instance that negotiates [HashAlgorithms][HashAlgorithm] used for revocation signatures
|
||||
* based on the given [Policy].
|
||||
*
|
||||
* @param policy algorithm policy
|
||||
* @return negotiator
|
||||
*/
|
||||
@JvmStatic
|
||||
fun negotiateRevocationSignatureAlgorithm(policy: Policy): HashAlgorithmNegotiator {
|
||||
return negotiateByPolicy(policy.revocationSignatureHashAlgorithmPolicy)
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an instance that negotiates [HashAlgorithms][HashAlgorithm] based on the given
|
||||
* [Policy.HashAlgorithmPolicy].
|
||||
*
|
||||
* @param hashAlgorithmPolicy algorithm policy for hash algorithms
|
||||
* @return negotiator
|
||||
*/
|
||||
@JvmStatic
|
||||
fun negotiateByPolicy(hashAlgorithmPolicy: Policy.HashAlgorithmPolicy): HashAlgorithmNegotiator {
|
||||
return object: HashAlgorithmNegotiator {
|
||||
override fun negotiateHashAlgorithm(orderedPrefs: Set<HashAlgorithm>): HashAlgorithm {
|
||||
return orderedPrefs.firstOrNull {
|
||||
hashAlgorithmPolicy.isAcceptable(it)
|
||||
} ?: hashAlgorithmPolicy.defaultHashAlgorithm()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.algorithm.negotiation
|
||||
|
||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm
|
||||
import org.pgpainless.policy.Policy
|
||||
import java.lang.IllegalArgumentException
|
||||
|
||||
interface SymmetricKeyAlgorithmNegotiator {
|
||||
|
||||
/**
|
||||
* Negotiate a symmetric encryption algorithm.
|
||||
* If the override is non-null, it will be returned instead of performing an actual negotiation.
|
||||
* Otherwise, the list of ordered sets containing the preferences of different recipient keys will be
|
||||
* used to determine a suitable symmetric encryption algorithm.
|
||||
*
|
||||
* @param policy algorithm policy
|
||||
* @param override algorithm override (if not null, return this)
|
||||
* @param keyPreferences list of preferences per key
|
||||
* @return negotiated algorithm
|
||||
*/
|
||||
fun negotiate(policy: Policy.SymmetricKeyAlgorithmPolicy,
|
||||
override: SymmetricKeyAlgorithm?,
|
||||
keyPreferences: List<Set<SymmetricKeyAlgorithm>>): SymmetricKeyAlgorithm
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun byPopularity(): SymmetricKeyAlgorithmNegotiator {
|
||||
return object: SymmetricKeyAlgorithmNegotiator {
|
||||
override fun negotiate(
|
||||
policy: Policy.SymmetricKeyAlgorithmPolicy,
|
||||
override: SymmetricKeyAlgorithm?,
|
||||
keyPreferences: List<Set<SymmetricKeyAlgorithm>>):
|
||||
SymmetricKeyAlgorithm {
|
||||
if (override == SymmetricKeyAlgorithm.NULL) {
|
||||
throw IllegalArgumentException("Algorithm override cannot be NULL (plaintext).")
|
||||
}
|
||||
|
||||
if (override != null) {
|
||||
return override
|
||||
}
|
||||
|
||||
// algorithm to #occurrences
|
||||
val supportWeight = buildMap {
|
||||
keyPreferences.forEach { keyPreference ->
|
||||
keyPreference.forEach { pref ->
|
||||
put(pref, getOrDefault(pref, 0) as Int + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pivot map and sort by popularity ascending
|
||||
// score to list(algo)
|
||||
val byScore = supportWeight.toList()
|
||||
.map { e -> e.second to e.first }
|
||||
.groupBy { e -> e.first }
|
||||
.map { e -> e.key to e.value.map { it.second }.toList() }
|
||||
.associate { e -> e }
|
||||
.toSortedMap()
|
||||
|
||||
// iterate in reverse over algorithms
|
||||
for (e in byScore.entries.reversed()) {
|
||||
val best = policy.selectBest(e.value)
|
||||
if (best != null) {
|
||||
return best
|
||||
}
|
||||
}
|
||||
|
||||
return policy.defaultSymmetricKeyAlgorithm
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.authentication
|
||||
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing
|
||||
|
||||
class CertificateAuthenticity(val userId: String,
|
||||
val certificate: PGPPublicKeyRing,
|
||||
val certificationChains: Map<CertificationChain, Int>,
|
||||
val targetAmount: Int) {
|
||||
|
||||
val totalTrustAmount: Int
|
||||
get() = certificationChains.values.sum()
|
||||
|
||||
|
||||
/**
|
||||
* Return the degree of authentication of the binding in percent.
|
||||
* 100% means full authentication.
|
||||
* Values smaller than 100% mean partial authentication.
|
||||
*
|
||||
* @return authenticity in percent
|
||||
*/
|
||||
val authenticityPercentage: Int
|
||||
get() = targetAmount * 100 / totalTrustAmount
|
||||
|
||||
/**
|
||||
* Return true, if the binding is authenticated to a sufficient degree.
|
||||
*
|
||||
* @return true if total gathered evidence outweighs the target trust amount.
|
||||
*/
|
||||
val authenticated: Boolean
|
||||
get() = targetAmount <= totalTrustAmount
|
||||
|
||||
fun isAuthenticated() = authenticated
|
||||
}
|
||||
|
||||
/**
|
||||
* A chain of certifications.
|
||||
*
|
||||
* @param trustAmount actual trust amount of the chain
|
||||
* @param chainLinks links of the chain, starting at the trust-root, ending at the target.
|
||||
*/
|
||||
class CertificationChain(
|
||||
val trustAmount: Int,
|
||||
val chainLinks: List<ChainLink>) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A chain link contains a node in the trust chain.
|
||||
*/
|
||||
class ChainLink(
|
||||
val certificate: PGPPublicKeyRing) {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.authentication;
|
||||
|
||||
import org.pgpainless.key.OpenPgpFingerprint
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Interface for a CA that can authenticate trust-worthy certificates.
|
||||
* Such a CA might be a fixed list of trustworthy certificates, or a dynamic implementation like the Web-of-Trust.
|
||||
*
|
||||
* @see <a href="https://github.com/pgpainless/pgpainless-wot">PGPainless-WOT</a>
|
||||
* @see <a href="https://sequoia-pgp.gitlab.io/sequoia-wot/">OpenPGP Web of Trust</a>
|
||||
*/
|
||||
interface CertificateAuthority {
|
||||
|
||||
/**
|
||||
* Determine the authenticity of the binding between the given fingerprint and the userId.
|
||||
* In other words, determine, how much evidence can be gathered, that the certificate with the given
|
||||
* fingerprint really belongs to the user with the given userId.
|
||||
*
|
||||
* @param fingerprint fingerprint of the certificate
|
||||
* @param userId userId
|
||||
* @param email if true, the userId will be treated as an email address and all user-IDs containing
|
||||
* the email address will be matched.
|
||||
* @param referenceTime reference time at which the binding shall be evaluated
|
||||
* @param targetAmount target trust amount (120 = fully authenticated, 240 = doubly authenticated,
|
||||
* 60 = partially authenticated...)
|
||||
* @return information about the authenticity of the binding
|
||||
*/
|
||||
fun authenticateBinding(fingerprint: OpenPgpFingerprint,
|
||||
userId: String,
|
||||
email: Boolean,
|
||||
referenceTime: Date,
|
||||
targetAmount: Int): CertificateAuthenticity;
|
||||
|
||||
/**
|
||||
* Lookup certificates, which carry a trustworthy binding to the given userId.
|
||||
*
|
||||
* @param userId userId
|
||||
* @param email if true, the user-ID will be treated as an email address and all user-IDs containing
|
||||
* the email address will be matched.
|
||||
* @param referenceTime reference time at which the binding shall be evaluated
|
||||
* @param targetAmount target trust amount (120 = fully authenticated, 240 = doubly authenticated,
|
||||
* 60 = partially authenticated...)
|
||||
* @return list of identified bindings
|
||||
*/
|
||||
fun lookupByUserId(userId: String,
|
||||
email: Boolean,
|
||||
referenceTime: Date,
|
||||
targetAmount: Int): List<CertificateAuthenticity>
|
||||
|
||||
/**
|
||||
* Identify trustworthy bindings for a certificate.
|
||||
* The result is a list of authenticatable userIds on the certificate.
|
||||
*
|
||||
* @param fingerprint fingerprint of the certificate
|
||||
* @param referenceTime reference time for trust calculations
|
||||
* @param targetAmount target trust amount (120 = fully authenticated, 240 = doubly authenticated,
|
||||
* 60 = partially authenticated...)
|
||||
* @return list of identified bindings
|
||||
*/
|
||||
fun identifyByFingerprint(fingerprint: OpenPgpFingerprint,
|
||||
referenceTime: Date,
|
||||
targetAmount: Int): List<CertificateAuthenticity>
|
||||
}
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.key
|
||||
|
||||
import org.bouncycastle.openpgp.PGPKeyRing
|
||||
import org.bouncycastle.openpgp.PGPPublicKey
|
||||
import org.bouncycastle.openpgp.PGPSecretKey
|
||||
import org.bouncycastle.util.encoders.Hex
|
||||
import java.nio.charset.Charset
|
||||
|
||||
/**
|
||||
* Abstract super class of different version OpenPGP fingerprints.
|
||||
*
|
||||
*/
|
||||
abstract class OpenPgpFingerprint : CharSequence, Comparable<OpenPgpFingerprint> {
|
||||
val fingerprint: String
|
||||
|
||||
/**
|
||||
* Return the version of the fingerprint.
|
||||
*
|
||||
* @return version
|
||||
*/
|
||||
abstract fun getVersion(): Int
|
||||
|
||||
/**
|
||||
* Return the key id of the OpenPGP public key this {@link OpenPgpFingerprint} belongs to.
|
||||
* This method can be implemented for V4 and V5 fingerprints.
|
||||
* V3 key-IDs cannot be derived from the fingerprint, but we don't care, since V3 is deprecated.
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc4880#section-12.2">
|
||||
* RFC-4880 §12.2: Key IDs and Fingerprints</a>
|
||||
* @return key id
|
||||
*/
|
||||
abstract val keyId: Long
|
||||
|
||||
constructor(fingerprint: String) {
|
||||
val prep = fingerprint.replace(" ", "").trim().uppercase()
|
||||
if (!isValid(prep)) {
|
||||
throw IllegalArgumentException("Fingerprint '$fingerprint' does not appear to be a valid OpenPGP V${getVersion()} fingerprint.")
|
||||
}
|
||||
this.fingerprint = prep
|
||||
}
|
||||
|
||||
constructor(bytes: ByteArray): this(Hex.toHexString(bytes))
|
||||
|
||||
constructor(key: PGPPublicKey): this(key.fingerprint) {
|
||||
if (key.version != getVersion()) {
|
||||
throw IllegalArgumentException("Key is not a v${getVersion()} OpenPgp key.")
|
||||
}
|
||||
}
|
||||
|
||||
constructor(key: PGPSecretKey): this(key.publicKey)
|
||||
|
||||
constructor(keys: PGPKeyRing): this(keys.publicKey)
|
||||
|
||||
/**
|
||||
* Check, whether the fingerprint consists of 40 valid hexadecimal characters.
|
||||
* @param fp fingerprint to check.
|
||||
* @return true if fingerprint is valid.
|
||||
*/
|
||||
protected abstract fun isValid(fingerprint: String): Boolean
|
||||
|
||||
override val length: Int
|
||||
get() = fingerprint.length
|
||||
|
||||
override fun get(index: Int) = fingerprint.get(index)
|
||||
|
||||
override fun subSequence(startIndex: Int, endIndex: Int) = fingerprint.subSequence(startIndex, endIndex)
|
||||
override fun compareTo(other: OpenPgpFingerprint): Int {
|
||||
return fingerprint.compareTo(other.fingerprint)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return toString() == other.toString()
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return toString().hashCode()
|
||||
}
|
||||
|
||||
override fun toString(): String = fingerprint
|
||||
|
||||
abstract fun prettyPrint(): String
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
val utf8: Charset = Charset.forName("UTF-8")
|
||||
|
||||
/**
|
||||
* Return the fingerprint of the given key.
|
||||
* This method automatically matches key versions to fingerprint implementations.
|
||||
*
|
||||
* @param key key
|
||||
* @return fingerprint
|
||||
*/
|
||||
@JvmStatic
|
||||
fun of(key: PGPSecretKey): OpenPgpFingerprint = of(key.publicKey)
|
||||
|
||||
/**
|
||||
* Return the fingerprint of the given key.
|
||||
* This method automatically matches key versions to fingerprint implementations.
|
||||
*
|
||||
* @param key key
|
||||
* @return fingerprint
|
||||
*/
|
||||
@JvmStatic
|
||||
fun of (key: PGPPublicKey): OpenPgpFingerprint = when(key.version) {
|
||||
4 -> OpenPgpV4Fingerprint(key)
|
||||
5 -> OpenPgpV5Fingerprint(key)
|
||||
6 -> OpenPgpV6Fingerprint(key)
|
||||
else -> throw IllegalArgumentException("OpenPGP keys of version ${key.version} are not supported.")
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the fingerprint of the primary key of the given key ring.
|
||||
* This method automatically matches key versions to fingerprint implementations.
|
||||
*
|
||||
* @param ring key ring
|
||||
* @return fingerprint
|
||||
*/
|
||||
@JvmStatic
|
||||
fun of (keys: PGPKeyRing): OpenPgpFingerprint = of(keys.publicKey)
|
||||
|
||||
/**
|
||||
* Try to parse an {@link OpenPgpFingerprint} from the given fingerprint string.
|
||||
* If the trimmed fingerprint without whitespace is 64 characters long, it is either a v5 or v6 fingerprint.
|
||||
* In this case, we return a {@link _64DigitFingerprint}. Since this is ambiguous, it is generally recommended
|
||||
* to know the version of the key beforehand.
|
||||
*
|
||||
* @param fingerprint fingerprint
|
||||
* @return parsed fingerprint
|
||||
* @deprecated Use the constructor methods of the versioned fingerprint subclasses instead.
|
||||
*/
|
||||
@JvmStatic
|
||||
@Deprecated("Use the constructor methods of the versioned fingerprint subclasses instead.")
|
||||
fun parse(fingerprint: String): OpenPgpFingerprint {
|
||||
val prep = fingerprint.replace(" ", "").trim().uppercase()
|
||||
if (prep.matches("^[0-9A-F]{40}$".toRegex())) {
|
||||
return OpenPgpV4Fingerprint(prep)
|
||||
}
|
||||
if (prep.matches("^[0-9A-F]{64}$".toRegex())) {
|
||||
// Might be v5 or v6 :/
|
||||
return _64DigitFingerprint(prep)
|
||||
}
|
||||
throw IllegalArgumentException("Fingerprint does not appear to match any known fingerprint pattern.")
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a binary OpenPGP fingerprint into an {@link OpenPgpFingerprint} object.
|
||||
*
|
||||
* @param binaryFingerprint binary representation of the fingerprint
|
||||
* @return parsed fingerprint
|
||||
* @deprecated use the parse() methods of the versioned fingerprint subclasses instead.
|
||||
*/
|
||||
@JvmStatic
|
||||
@Deprecated("use the parse() methods of the versioned fingerprint subclasses instead.")
|
||||
fun parseFromBinary(binaryFingerprint: ByteArray): OpenPgpFingerprint =
|
||||
parse(Hex.toHexString(binaryFingerprint))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.key
|
||||
|
||||
import org.bouncycastle.openpgp.PGPKeyRing
|
||||
import org.bouncycastle.openpgp.PGPPublicKey
|
||||
import org.bouncycastle.openpgp.PGPSecretKey
|
||||
import org.bouncycastle.util.encoders.Hex
|
||||
import java.net.URI
|
||||
import java.nio.Buffer
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.charset.Charset
|
||||
|
||||
class OpenPgpV4Fingerprint: OpenPgpFingerprint {
|
||||
|
||||
constructor(fingerprint: String): super(fingerprint)
|
||||
constructor(bytes: ByteArray): super(bytes)
|
||||
constructor(key: PGPPublicKey): super(key)
|
||||
constructor(key: PGPSecretKey): super(key)
|
||||
constructor(keys: PGPKeyRing): super(keys)
|
||||
|
||||
override fun getVersion() = 4
|
||||
|
||||
override val keyId: Long
|
||||
get() {
|
||||
val bytes = Hex.decode(toString().toByteArray(Charset.forName("UTF-8")))
|
||||
val buf = ByteBuffer.wrap(bytes)
|
||||
|
||||
// The key id is the right-most 8 bytes (conveniently a long)
|
||||
// We have to cast here in order to be compatible with java 8
|
||||
// https://github.com/eclipse/jetty.project/issues/3244
|
||||
(buf as Buffer).position(12) // 20 - 8 bytes = offset 12
|
||||
return buf.getLong()
|
||||
}
|
||||
|
||||
override fun isValid(fingerprint: String): Boolean {
|
||||
return fingerprint.matches("^[0-9A-F]{40}$".toRegex())
|
||||
}
|
||||
|
||||
fun toUri(): URI = URI(SCHEME, toString(), null)
|
||||
|
||||
override fun prettyPrint(): String {
|
||||
return buildString {
|
||||
for (i in 0..4) {
|
||||
append(fingerprint, i * 4, (i + 1) * 4).append(' ')
|
||||
}
|
||||
append(' ')
|
||||
for (i in 5 .. 8) {
|
||||
append(fingerprint, i * 4, (i + 1) * 4).append(' ')
|
||||
}
|
||||
append(fingerprint, 36, 40)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
val SCHEME = "openpgp4fpr"
|
||||
|
||||
@JvmStatic
|
||||
fun fromUri(uri: URI): OpenPgpV4Fingerprint {
|
||||
if (SCHEME != uri.scheme) {
|
||||
throw IllegalArgumentException("URI scheme MUST equal '$SCHEME'.")
|
||||
}
|
||||
return OpenPgpV4Fingerprint(uri.schemeSpecificPart)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.key
|
||||
|
||||
import org.bouncycastle.openpgp.PGPKeyRing
|
||||
import org.bouncycastle.openpgp.PGPPublicKey
|
||||
import org.bouncycastle.openpgp.PGPSecretKey
|
||||
|
||||
/**
|
||||
* This class represents a hex encoded uppercase OpenPGP v5 fingerprint.
|
||||
*/
|
||||
class OpenPgpV5Fingerprint: _64DigitFingerprint {
|
||||
|
||||
constructor(fingerprint: String): super(fingerprint)
|
||||
constructor(key: PGPPublicKey): super(key)
|
||||
constructor(key: PGPSecretKey): super(key)
|
||||
constructor(keys: PGPKeyRing): super(keys)
|
||||
constructor(bytes: ByteArray): super(bytes)
|
||||
|
||||
override fun getVersion(): Int {
|
||||
return 5
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.key
|
||||
|
||||
import org.bouncycastle.openpgp.PGPKeyRing
|
||||
import org.bouncycastle.openpgp.PGPPublicKey
|
||||
import org.bouncycastle.openpgp.PGPSecretKey
|
||||
|
||||
/**
|
||||
* This class represents a hex encoded, uppercase OpenPGP v6 fingerprint.
|
||||
*/
|
||||
class OpenPgpV6Fingerprint: _64DigitFingerprint {
|
||||
|
||||
constructor(fingerprint: String): super(fingerprint)
|
||||
constructor(key: PGPPublicKey): super(key)
|
||||
constructor(key: PGPSecretKey): super(key)
|
||||
constructor(keys: PGPKeyRing): super(keys)
|
||||
constructor(bytes: ByteArray): super(bytes)
|
||||
|
||||
override fun getVersion(): Int {
|
||||
return 6
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.key
|
||||
|
||||
import org.bouncycastle.openpgp.PGPKeyRing
|
||||
import org.bouncycastle.openpgp.PGPPublicKey
|
||||
import org.pgpainless.key.util.KeyIdUtil
|
||||
|
||||
/**
|
||||
* Tuple class used to identify a subkey by fingerprints of the primary key of the subkeys key ring,
|
||||
* as well as the subkeys fingerprint.
|
||||
*/
|
||||
class SubkeyIdentifier(
|
||||
val primaryKeyFingerprint: OpenPgpFingerprint,
|
||||
val subkeyFingerprint: OpenPgpFingerprint) {
|
||||
|
||||
constructor(fingerprint: OpenPgpFingerprint): this(fingerprint, fingerprint)
|
||||
constructor(keys: PGPKeyRing): this(keys.publicKey)
|
||||
constructor(key: PGPPublicKey): this(OpenPgpFingerprint.of(key))
|
||||
constructor(keys: PGPKeyRing, keyId: Long): this(
|
||||
OpenPgpFingerprint.of(keys.publicKey),
|
||||
OpenPgpFingerprint.of(keys.getPublicKey(keyId) ?:
|
||||
throw NoSuchElementException("OpenPGP key does not contain subkey ${KeyIdUtil.formatKeyId(keyId)}")))
|
||||
constructor(keys: PGPKeyRing, subkeyFingerprint: OpenPgpFingerprint): this(OpenPgpFingerprint.of(keys), subkeyFingerprint)
|
||||
|
||||
val keyId = subkeyFingerprint.keyId
|
||||
val fingerprint = subkeyFingerprint
|
||||
|
||||
val subkeyId = subkeyFingerprint.keyId
|
||||
val primaryKeyId = primaryKeyFingerprint.keyId
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (other == null) {
|
||||
return false
|
||||
}
|
||||
if (this === other) {
|
||||
return true
|
||||
}
|
||||
if (other !is SubkeyIdentifier) {
|
||||
return false
|
||||
}
|
||||
|
||||
return primaryKeyFingerprint.equals(other.primaryKeyFingerprint) && subkeyFingerprint.equals(other.subkeyFingerprint)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return primaryKeyFingerprint.hashCode() + 31 * subkeyFingerprint.hashCode()
|
||||
}
|
||||
|
||||
override fun toString(): String = "$subkeyFingerprint $primaryKeyFingerprint"
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.key;
|
||||
|
||||
import java.nio.Buffer;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
|
||||
/**
|
||||
* This class represents a hex encoded, upper case OpenPGP v5 or v6 fingerprint.
|
||||
* Since both fingerprints use the same format, this class is used when parsing the fingerprint without knowing the
|
||||
* key version.
|
||||
*/
|
||||
open class _64DigitFingerprint: OpenPgpFingerprint {
|
||||
|
||||
/**
|
||||
* Create an {@link _64DigitFingerprint}.
|
||||
*
|
||||
* @param fingerprint uppercase hexadecimal fingerprint of length 64
|
||||
*/
|
||||
constructor(fingerprint: String): super(fingerprint)
|
||||
constructor(bytes: ByteArray): super(bytes)
|
||||
constructor(key: PGPPublicKey): super(key)
|
||||
constructor(key: PGPSecretKey): super(key)
|
||||
constructor(keys: PGPKeyRing): super(keys)
|
||||
|
||||
override val keyId: Long
|
||||
get() {
|
||||
val bytes = Hex.decode(fingerprint.toByteArray(Charset.forName("UTF-8")))
|
||||
val buf = ByteBuffer.wrap(bytes);
|
||||
|
||||
// The key id is the left-most 8 bytes (conveniently a long).
|
||||
// We have to cast here in order to be compatible with java 8
|
||||
// https://github.com/eclipse/jetty.project/issues/3244
|
||||
(buf as Buffer).position(0);
|
||||
|
||||
return buf.getLong()
|
||||
}
|
||||
|
||||
override fun getVersion(): Int {
|
||||
return -1 // might be v5 or v6
|
||||
}
|
||||
|
||||
override fun isValid(fingerprint: String): Boolean {
|
||||
return fingerprint.matches(("^[0-9A-F]{64}$".toRegex()))
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return super.toString()
|
||||
}
|
||||
|
||||
override fun prettyPrint(): String {
|
||||
return buildString {
|
||||
for (i in 0 until 4) {
|
||||
append(fingerprint, i * 8, (i + 1) * 8).append(' ');
|
||||
}
|
||||
append(' ');
|
||||
for (i in 4 until 7) {
|
||||
append(fingerprint, i * 8, (i + 1) * 8).append(' ');
|
||||
}
|
||||
append(fingerprint, 56, 64);
|
||||
}
|
||||
}
|
||||
}
|
||||
359
pgpainless-core/src/main/kotlin/org/pgpainless/policy/Policy.kt
Normal file
359
pgpainless-core/src/main/kotlin/org/pgpainless/policy/Policy.kt
Normal file
|
|
@ -0,0 +1,359 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.policy
|
||||
|
||||
import org.pgpainless.algorithm.*
|
||||
import org.pgpainless.util.DateUtil
|
||||
import org.pgpainless.util.NotationRegistry
|
||||
import java.util.*
|
||||
|
||||
class Policy(
|
||||
var signatureHashAlgorithmPolicy: HashAlgorithmPolicy,
|
||||
var revocationSignatureHashAlgorithmPolicy: HashAlgorithmPolicy,
|
||||
var symmetricKeyEncryptionAlgorithmPolicy: SymmetricKeyAlgorithmPolicy,
|
||||
var symmetricKeyDecryptionAlgorithmPolicy: SymmetricKeyAlgorithmPolicy,
|
||||
var compressionAlgorithmPolicy: CompressionAlgorithmPolicy,
|
||||
var publicKeyAlgorithmPolicy: PublicKeyAlgorithmPolicy,
|
||||
var notationRegistry: NotationRegistry) {
|
||||
|
||||
constructor(): this(
|
||||
HashAlgorithmPolicy.smartSignatureHashAlgorithmPolicy(),
|
||||
HashAlgorithmPolicy.smartSignatureHashAlgorithmPolicy(),
|
||||
SymmetricKeyAlgorithmPolicy.symmetricKeyEncryptionPolicy2022(),
|
||||
SymmetricKeyAlgorithmPolicy.symmetricKeyDecryptionPolicy2022(),
|
||||
CompressionAlgorithmPolicy.anyCompressionAlgorithmPolicy(),
|
||||
PublicKeyAlgorithmPolicy.bsi2021PublicKeyAlgorithmPolicy(),
|
||||
NotationRegistry())
|
||||
|
||||
var keyGenerationAlgorithmSuite = AlgorithmSuite.defaultAlgorithmSuite
|
||||
var signerUserIdValidationLevel = SignerUserIdValidationLevel.DISABLED
|
||||
var enableKeyParameterValidation = false
|
||||
|
||||
fun isEnableKeyParameterValidation() = enableKeyParameterValidation
|
||||
|
||||
|
||||
/**
|
||||
* Create a HashAlgorithmPolicy which accepts all [HashAlgorithms][HashAlgorithm] from the
|
||||
* given map, if the queried usage date is BEFORE the respective termination date.
|
||||
* A termination date value of <pre>null</pre> means no termination, resulting in the algorithm being
|
||||
* acceptable, regardless of usage date.
|
||||
*
|
||||
* @param defaultHashAlgorithm default hash algorithm
|
||||
* @param algorithmTerminationDates map of acceptable algorithms and their termination dates
|
||||
*/
|
||||
class HashAlgorithmPolicy(
|
||||
val defaultHashAlgorithm: HashAlgorithm,
|
||||
val acceptableHashAlgorithmsAndTerminationDates: Map<HashAlgorithm, Date?>) {
|
||||
|
||||
/**
|
||||
* Create a [HashAlgorithmPolicy] which accepts all [HashAlgorithms][HashAlgorithm] listed in
|
||||
* the given list, regardless of usage date.
|
||||
*
|
||||
* @param defaultHashAlgorithm default hash algorithm (e.g. used as fallback if negotiation fails)
|
||||
* @param acceptableHashAlgorithms list of acceptable hash algorithms
|
||||
*/
|
||||
constructor(defaultHashAlgorithm: HashAlgorithm, acceptableHashAlgorithms: List<HashAlgorithm>) :
|
||||
this(defaultHashAlgorithm, acceptableHashAlgorithms.associateWith { null })
|
||||
|
||||
fun isAcceptable(hashAlgorithm: HashAlgorithm) = isAcceptable(hashAlgorithm, Date())
|
||||
|
||||
/**
|
||||
* Return true, if the given algorithm is acceptable for the given usage date.
|
||||
*
|
||||
* @param hashAlgorithm algorithm
|
||||
* @param referenceTime usage date (e.g. signature creation time)
|
||||
*
|
||||
* @return acceptance
|
||||
*/
|
||||
fun isAcceptable(hashAlgorithm: HashAlgorithm, referenceTime: Date): Boolean {
|
||||
if (!acceptableHashAlgorithmsAndTerminationDates.containsKey(hashAlgorithm))
|
||||
return false
|
||||
val terminationDate = acceptableHashAlgorithmsAndTerminationDates[hashAlgorithm] ?: return true
|
||||
return terminationDate > referenceTime
|
||||
}
|
||||
|
||||
fun isAcceptable(algorithmId: Int) = isAcceptable(algorithmId, Date())
|
||||
|
||||
fun isAcceptable(algorithmId: Int, referenceTime: Date): Boolean {
|
||||
val algorithm = HashAlgorithm.fromId(algorithmId) ?: return false
|
||||
return isAcceptable(algorithm, referenceTime)
|
||||
}
|
||||
|
||||
fun defaultHashAlgorithm() = defaultHashAlgorithm
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun smartSignatureHashAlgorithmPolicy() = HashAlgorithmPolicy(
|
||||
HashAlgorithm.SHA512, buildMap {
|
||||
put(HashAlgorithm.SHA3_512, null)
|
||||
put(HashAlgorithm.SHA3_256, null)
|
||||
put(HashAlgorithm.SHA512, null)
|
||||
put(HashAlgorithm.SHA384, null)
|
||||
put(HashAlgorithm.SHA256, null)
|
||||
put(HashAlgorithm.SHA224, null)
|
||||
put(HashAlgorithm.RIPEMD160, DateUtil.parseUTCDate("2013-02-01 00:00:00 UTC"))
|
||||
put(HashAlgorithm.SHA1, DateUtil.parseUTCDate("2013-02-01 00:00:00 UTC"))
|
||||
put(HashAlgorithm.MD5, DateUtil.parseUTCDate("1997-02-01 00:00:00 UTC"))
|
||||
})
|
||||
|
||||
/**
|
||||
* [HashAlgorithmPolicy] which only accepts signatures made using algorithms which are acceptable
|
||||
* according to 2022 standards.
|
||||
*
|
||||
* Particularly this policy only accepts algorithms from the SHA2 and SHA3 families.
|
||||
*
|
||||
* @return static signature algorithm policy
|
||||
*/
|
||||
@JvmStatic
|
||||
fun static2022SignatureHashAlgorithmPolicy() = HashAlgorithmPolicy(
|
||||
HashAlgorithm.SHA512,
|
||||
listOf(
|
||||
HashAlgorithm.SHA3_512,
|
||||
HashAlgorithm.SHA3_256,
|
||||
HashAlgorithm.SHA512,
|
||||
HashAlgorithm.SHA384,
|
||||
HashAlgorithm.SHA256,
|
||||
HashAlgorithm.SHA224)
|
||||
)
|
||||
|
||||
/**
|
||||
* Hash algorithm policy for revocation signatures, which accepts SHA1 and SHA2 algorithms, as well as RIPEMD160.
|
||||
*
|
||||
* @return static revocation signature hash algorithm policy
|
||||
*/
|
||||
@JvmStatic
|
||||
fun static2022RevocationSignatureHashAlgorithmPolicy() = HashAlgorithmPolicy(
|
||||
HashAlgorithm.SHA512,
|
||||
listOf(
|
||||
HashAlgorithm.SHA3_512,
|
||||
HashAlgorithm.SHA3_256,
|
||||
HashAlgorithm.SHA512,
|
||||
HashAlgorithm.SHA384,
|
||||
HashAlgorithm.SHA256,
|
||||
HashAlgorithm.SHA224,
|
||||
HashAlgorithm.SHA1,
|
||||
HashAlgorithm.RIPEMD160
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class SymmetricKeyAlgorithmPolicy(
|
||||
val defaultSymmetricKeyAlgorithm: SymmetricKeyAlgorithm,
|
||||
val acceptableSymmetricKeyAlgorithms: List<SymmetricKeyAlgorithm>) {
|
||||
|
||||
fun isAcceptable(algorithm: SymmetricKeyAlgorithm) = acceptableSymmetricKeyAlgorithms.contains(algorithm)
|
||||
fun isAcceptable(algorithmId: Int): Boolean {
|
||||
val algorithm = SymmetricKeyAlgorithm.fromId(algorithmId) ?: return false
|
||||
return isAcceptable(algorithm)
|
||||
}
|
||||
|
||||
fun selectBest(options: List<SymmetricKeyAlgorithm>): SymmetricKeyAlgorithm? {
|
||||
for (acceptable in acceptableSymmetricKeyAlgorithms) {
|
||||
if (options.contains(acceptable)) {
|
||||
return acceptable
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* The default symmetric encryption algorithm policy of PGPainless.
|
||||
*
|
||||
* @return default symmetric encryption algorithm policy
|
||||
* @deprecated not expressive - will be removed in a future release
|
||||
*/
|
||||
@JvmStatic
|
||||
@Deprecated(
|
||||
"Not expressive - will be removed in a future release",
|
||||
ReplaceWith("symmetricKeyEncryptionPolicy2022"))
|
||||
fun defaultSymmetricKeyEncryptionAlgorithmPolicy() = symmetricKeyEncryptionPolicy2022()
|
||||
|
||||
/**
|
||||
* Policy for symmetric encryption algorithms in the context of message production (encryption).
|
||||
* This suite contains algorithms that are deemed safe to use in 2022.
|
||||
*
|
||||
* @return 2022 symmetric key encryption algorithm policy
|
||||
*/
|
||||
@JvmStatic
|
||||
fun symmetricKeyEncryptionPolicy2022() = SymmetricKeyAlgorithmPolicy(
|
||||
SymmetricKeyAlgorithm.AES_128,
|
||||
// Reject: Unencrypted, IDEA, TripleDES, CAST5, Blowfish
|
||||
listOf(
|
||||
SymmetricKeyAlgorithm.AES_256,
|
||||
SymmetricKeyAlgorithm.AES_192,
|
||||
SymmetricKeyAlgorithm.AES_128,
|
||||
SymmetricKeyAlgorithm.TWOFISH,
|
||||
SymmetricKeyAlgorithm.CAMELLIA_256,
|
||||
SymmetricKeyAlgorithm.CAMELLIA_192,
|
||||
SymmetricKeyAlgorithm.CAMELLIA_128
|
||||
))
|
||||
|
||||
/**
|
||||
* The default symmetric decryption algorithm policy of PGPainless.
|
||||
*
|
||||
* @return default symmetric decryption algorithm policy
|
||||
* @deprecated not expressive - will be removed in a future update
|
||||
*/
|
||||
@JvmStatic
|
||||
@Deprecated("not expressive - will be removed in a future update",
|
||||
ReplaceWith("symmetricKeyDecryptionPolicy2022()"))
|
||||
fun defaultSymmetricKeyDecryptionAlgorithmPolicy() = symmetricKeyDecryptionPolicy2022()
|
||||
|
||||
/**
|
||||
* Policy for symmetric key encryption algorithms in the context of message consumption (decryption).
|
||||
* This suite contains algorithms that are deemed safe to use in 2022.
|
||||
*
|
||||
* @return 2022 symmetric key decryption algorithm policy
|
||||
*/
|
||||
@JvmStatic
|
||||
fun symmetricKeyDecryptionPolicy2022() = SymmetricKeyAlgorithmPolicy(
|
||||
SymmetricKeyAlgorithm.AES_128,
|
||||
// Reject: Unencrypted, IDEA, TripleDES, Blowfish
|
||||
listOf(
|
||||
SymmetricKeyAlgorithm.AES_256,
|
||||
SymmetricKeyAlgorithm.AES_192,
|
||||
SymmetricKeyAlgorithm.AES_128,
|
||||
SymmetricKeyAlgorithm.TWOFISH,
|
||||
SymmetricKeyAlgorithm.CAMELLIA_256,
|
||||
SymmetricKeyAlgorithm.CAMELLIA_192,
|
||||
SymmetricKeyAlgorithm.CAMELLIA_128,
|
||||
SymmetricKeyAlgorithm.CAST5
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
class CompressionAlgorithmPolicy(
|
||||
val defaultCompressionAlgorithm: CompressionAlgorithm,
|
||||
val acceptableCompressionAlgorithms: List<CompressionAlgorithm>) {
|
||||
|
||||
fun isAcceptable(algorithm: CompressionAlgorithm) = acceptableCompressionAlgorithms.contains(algorithm)
|
||||
fun isAcceptable(algorithmId: Int): Boolean {
|
||||
val algorithm = CompressionAlgorithm.fromId(algorithmId) ?: return false
|
||||
return isAcceptable(algorithm)
|
||||
}
|
||||
|
||||
fun defaultCompressionAlgorithm() = defaultCompressionAlgorithm
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Default {@link CompressionAlgorithmPolicy} of PGPainless.
|
||||
* The default compression algorithm policy accepts any compression algorithm.
|
||||
*
|
||||
* @return default algorithm policy
|
||||
* @deprecated not expressive - might be removed in a future release
|
||||
*/
|
||||
@JvmStatic
|
||||
@Deprecated("not expressive - might be removed in a future release",
|
||||
ReplaceWith("anyCompressionAlgorithmPolicy()"))
|
||||
fun defaultCompressionAlgorithmPolicy() = anyCompressionAlgorithmPolicy()
|
||||
|
||||
/**
|
||||
* Policy that accepts any known compression algorithm and offers [CompressionAlgorithm.ZIP] as
|
||||
* default algorithm.
|
||||
*
|
||||
* @return compression algorithm policy
|
||||
*/
|
||||
@JvmStatic
|
||||
fun anyCompressionAlgorithmPolicy() = CompressionAlgorithmPolicy(
|
||||
CompressionAlgorithm.ZIP,
|
||||
listOf(CompressionAlgorithm.UNCOMPRESSED,
|
||||
CompressionAlgorithm.ZIP,
|
||||
CompressionAlgorithm.BZIP2,
|
||||
CompressionAlgorithm.ZLIB))
|
||||
}
|
||||
}
|
||||
|
||||
class PublicKeyAlgorithmPolicy(private val algorithmStrengths: Map<PublicKeyAlgorithm, Int>) {
|
||||
|
||||
fun isAcceptable(algorithm: PublicKeyAlgorithm, bitStrength: Int): Boolean {
|
||||
return bitStrength >= (algorithmStrengths[algorithm] ?: return false)
|
||||
}
|
||||
|
||||
fun isAcceptable(algorithmId: Int, bitStrength: Int): Boolean {
|
||||
val algorithm = PublicKeyAlgorithm.fromId(algorithmId) ?: return false
|
||||
return isAcceptable(algorithm, bitStrength)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Return PGPainless' default public key algorithm policy.
|
||||
* This policy is based upon recommendations made by the German Federal Office for Information Security (BSI).
|
||||
*
|
||||
* @return default algorithm policy
|
||||
* @deprecated not expressive - might be removed in a future release
|
||||
*/
|
||||
@JvmStatic
|
||||
@Deprecated("not expressive - might be removed in a future release",
|
||||
ReplaceWith("bsi2021PublicKeyAlgorithmPolicy()"))
|
||||
fun defaultPublicKeyAlgorithmPolicy() = bsi2021PublicKeyAlgorithmPolicy()
|
||||
|
||||
/**
|
||||
* This policy is based upon recommendations made by the German Federal Office for Information Security (BSI).
|
||||
*
|
||||
* Basically this policy requires keys based on elliptic curves to have a bit strength of at least 250,
|
||||
* and keys based on prime number factorization / discrete logarithm problems to have a strength of at least 2000 bits.
|
||||
*
|
||||
* @see <a href="https://www.bsi.bund.de/SharedDocs/Downloads/EN/BSI/Publications/TechGuidelines/TG02102/BSI-TR-02102-1.pdf">BSI - Technical Guideline - Cryptographic Mechanisms: Recommendations and Key Lengths (2021-01)</a>
|
||||
* @see <a href="https://www.keylength.com/">BlueKrypt | Cryptographic Key Length Recommendation</a>
|
||||
*
|
||||
* @return default algorithm policy
|
||||
*/
|
||||
@JvmStatic
|
||||
fun bsi2021PublicKeyAlgorithmPolicy() = PublicKeyAlgorithmPolicy(buildMap {
|
||||
// §5.4.1
|
||||
put(PublicKeyAlgorithm.RSA_GENERAL, 2000)
|
||||
put(PublicKeyAlgorithm.RSA_SIGN, 2000)
|
||||
put(PublicKeyAlgorithm.RSA_ENCRYPT, 2000)
|
||||
// Note: ElGamal is not mentioned in the BSI document.
|
||||
// We assume that the requirements are similar to other DH algorithms
|
||||
put(PublicKeyAlgorithm.ELGAMAL_ENCRYPT, 2000)
|
||||
put(PublicKeyAlgorithm.ELGAMAL_GENERAL, 2000)
|
||||
// §5.4.2
|
||||
put(PublicKeyAlgorithm.DSA, 2000)
|
||||
// §5.4.3
|
||||
put(PublicKeyAlgorithm.ECDSA, 250)
|
||||
// Note: EdDSA is not mentioned in the BSI document.
|
||||
// We assume that the requirements are similar to other EC algorithms.
|
||||
put(PublicKeyAlgorithm.EDDSA, 250)
|
||||
// §7.2.1
|
||||
put(PublicKeyAlgorithm.DIFFIE_HELLMAN, 2000)
|
||||
// §7.2.2
|
||||
put(PublicKeyAlgorithm.ECDH, 250)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
enum class SignerUserIdValidationLevel {
|
||||
/**
|
||||
* PGPainless will verify {@link org.bouncycastle.bcpg.sig.SignerUserID} subpackets in signatures strictly.
|
||||
* This means, that signatures with Signer's User-ID subpackets containing a value that does not match the signer key's
|
||||
* user-id exactly, will be rejected.
|
||||
* E.g. Signer's user-id "alice@pgpainless.org", User-ID: "Alice <alice@pgpainless.org>" does not
|
||||
* match exactly and is therefore rejected.
|
||||
*/
|
||||
STRICT,
|
||||
|
||||
/**
|
||||
* PGPainless will ignore {@link org.bouncycastle.bcpg.sig.SignerUserID} subpackets on signature.
|
||||
*/
|
||||
DISABLED
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@Volatile
|
||||
private var INSTANCE: Policy? = null
|
||||
|
||||
@JvmStatic
|
||||
fun getInstance() = INSTANCE ?: synchronized(this) {
|
||||
INSTANCE ?: Policy().also { INSTANCE = it }
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue