Improve Key Generation API

This commit is contained in:
Paul Schaub 2024-02-29 14:40:06 +01:00
parent 20002efbf6
commit b4240ac9f7
Signed by: vanitasvitae
GPG Key ID: 62BEE9264BF17311
7 changed files with 219 additions and 154 deletions

View File

@ -0,0 +1,24 @@
// SPDX-FileCopyrightText: 2024 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package openpgp
import org.pgpainless.util.Passphrase
/**
* Extension function to convert a nullable [CharSequence] into an [Array]. Iff [this] is `null`,
* then return an empty array, otherwise return an array only consisting of [this].
*
* @return array
*/
fun CharSequence?.toArray(): Array<CharSequence> = this?.let { arrayOf(it) } ?: emptyArray()
/**
* Return a [Passphrase] from this [CharSequence]. Iff [this] is `null` or blank, then this method
* returns [Passphrase.emptyPassphrase], otherwise it returns [Passphrase.fromPassword].
*
* @return passphrase
*/
fun CharSequence?.toPassphrase(): Passphrase =
this?.let { Passphrase.fromPassword(it) } ?: Passphrase.emptyPassphrase()

View File

@ -15,6 +15,7 @@ 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.generation.OpenPgpKeyGenerator
import org.pgpainless.key.info.KeyRingInfo
import org.pgpainless.key.modification.secretkeyring.SecretKeyRingEditor
import org.pgpainless.key.parsing.KeyRingReader
@ -26,19 +27,30 @@ class PGPainless private constructor() {
companion object {
/** Generate an OpenPGP key. */
@JvmOverloads
@JvmStatic
fun generateOpenPgpKey(policy: Policy = getPolicy()) = OpenPgpKeyGenerator(policy)
/**
* Generate a fresh OpenPGP key ring from predefined templates.
*
* @return templates
*/
@JvmStatic fun generateKeyRing() = KeyRingTemplates()
@Deprecated(
"Deprecated in favor of new API",
ReplaceWith("generateOpenPgpKey().buildV4Key().fromTemplate()"))
@JvmStatic
fun generateKeyRing() = KeyRingTemplates()
/**
* Build a custom OpenPGP key ring.
*
* @return builder
*/
@JvmStatic fun buildKeyRing() = KeyRingBuilder()
@Deprecated("Deprecated in favor of new API", ReplaceWith("generateOpenPgpKey()"))
@JvmStatic
fun buildKeyRing() = KeyRingBuilder()
/**
* Read an existing OpenPGP key ring.

View File

@ -4,6 +4,8 @@
package org.pgpainless.key.generation
import openpgp.toArray
import openpgp.toPassphrase
import org.bouncycastle.openpgp.PGPSecretKeyRing
import org.pgpainless.algorithm.KeyFlag
import org.pgpainless.key.generation.type.KeyType
@ -30,15 +32,13 @@ class KeyRingTemplates {
length: RsaLength,
passphrase: Passphrase = Passphrase.emptyPassphrase()
): PGPSecretKeyRing =
OpenPgpKeyGenerator.buildV4Key()
.setPrimaryKey(KeyType.RSA(length), listOf(KeyFlag.CERTIFY_OTHER)) {
if (userId != null) {
addUserId(userId)
}
}
.addSigningSubkey(KeyType.RSA(length))
.addEncryptionSubkey(KeyType.RSA(length))
.build(passphrase)
OpenPgpKeyGenerator()
.buildV4Key()
.fromTemplate()
.composedRsa(
userId = userId.toArray(),
length = length,
protector = SecretKeyRingProtector.unlockAnyKeyWith(passphrase))
/**
* Generate an RSA OpenPGP key consisting of an RSA primary key used for certification, a
@ -50,14 +50,11 @@ class KeyRingTemplates {
* keys.
* @return key
*/
fun rsaKeyRing(userId: CharSequence?, length: RsaLength, password: String?): PGPSecretKeyRing =
password.let {
if (it.isNullOrBlank()) {
rsaKeyRing(userId, length, Passphrase.emptyPassphrase())
} else {
rsaKeyRing(userId, length, Passphrase.fromPassword(it))
}
}
fun rsaKeyRing(
userId: CharSequence?,
length: RsaLength,
password: CharSequence?
): PGPSecretKeyRing = rsaKeyRing(userId, length, password.toPassphrase())
/**
* Creates a simple RSA KeyPair of length {@code length} with user-id {@code userId}. The
@ -75,19 +72,13 @@ class KeyRingTemplates {
length: RsaLength,
passphrase: Passphrase = Passphrase.emptyPassphrase()
): PGPSecretKeyRing =
OpenPgpKeyGenerator.buildV4Key()
.setPrimaryKey(
KeyType.RSA(length),
listOf(
KeyFlag.CERTIFY_OTHER,
KeyFlag.SIGN_DATA,
KeyFlag.ENCRYPT_COMMS,
KeyFlag.ENCRYPT_STORAGE)) {
if (userId != null) {
addUserId(userId)
}
}
.build(passphrase)
OpenPgpKeyGenerator()
.buildV4Key()
.fromTemplate()
.singleRsa(
userId = userId.toArray(),
length = length,
protector = SecretKeyRingProtector.unlockAnyKeyWith(passphrase))
/**
* Creates a simple RSA KeyPair of length {@code length} with user-id {@code userId}. The
@ -99,14 +90,8 @@ class KeyRingTemplates {
* @param password Password of the key. Can be null or blank for unencrypted keys.
* @return {@link PGPSecretKeyRing} containing the KeyPair.
*/
fun simpleRsaKeyRing(userId: CharSequence?, length: RsaLength, password: String?) =
password.let {
if (it.isNullOrBlank()) {
simpleRsaKeyRing(userId, length, Passphrase.emptyPassphrase())
} else {
simpleRsaKeyRing(userId, length, Passphrase.fromPassword(it))
}
}
fun simpleRsaKeyRing(userId: CharSequence?, length: RsaLength, password: CharSequence?) =
simpleRsaKeyRing(userId, length, password.toPassphrase())
/**
* Creates a key ring consisting of an ed25519 EdDSA primary key and a X25519 XDH subkey. The
@ -122,7 +107,8 @@ class KeyRingTemplates {
userId: CharSequence?,
passphrase: Passphrase = Passphrase.emptyPassphrase()
): PGPSecretKeyRing =
OpenPgpKeyGenerator.buildV4Key()
OpenPgpKeyGenerator()
.buildV4Key()
.setPrimaryKey(
KeyType.EDDSA(EdDSACurve._Ed25519),
listOf(KeyFlag.CERTIFY_OTHER, KeyFlag.SIGN_DATA)) {
@ -142,14 +128,8 @@ class KeyRingTemplates {
* @param password Password of the private key. Can be null or blank for an unencrypted key.
* @return {@link PGPSecretKeyRing} containing the key pairs.
*/
fun simpleEcKeyRing(userId: CharSequence?, password: String?): PGPSecretKeyRing =
password.let {
if (it.isNullOrBlank()) {
simpleEcKeyRing(userId, Passphrase.emptyPassphrase())
} else {
simpleEcKeyRing(userId, Passphrase.fromPassword(it))
}
}
fun simpleEcKeyRing(userId: CharSequence?, password: CharSequence?): PGPSecretKeyRing =
simpleEcKeyRing(userId, password.toPassphrase())
/**
* Generate a modern PGP key ring consisting of an ed25519 EdDSA primary key which is used to
@ -164,17 +144,12 @@ class KeyRingTemplates {
userId: CharSequence?,
passphrase: Passphrase = Passphrase.emptyPassphrase()
): PGPSecretKeyRing =
OpenPgpKeyGenerator.buildV4Key()
.setPrimaryKey(KeyType.EDDSA(EdDSACurve._Ed25519), listOf(KeyFlag.CERTIFY_OTHER)) {
if (userId != null) {
addUserId(userId)
}
}
.addEncryptionSubkey(KeyType.XDH(XDHSpec._X25519))
.addSigningSubkey(KeyType.EDDSA(EdDSACurve._Ed25519))
.build(
if (passphrase.isEmpty) SecretKeyRingProtector.unprotectedKeys()
else SecretKeyRingProtector.unlockAnyKeyWith(passphrase))
OpenPgpKeyGenerator()
.buildV4Key()
.fromTemplate()
.ed25519Curve25519(
userId = userId.toArray(),
protector = SecretKeyRingProtector.unlockAnyKeyWith(passphrase))
/**
* Generate a modern PGP key ring consisting of an ed25519 EdDSA primary key which is used to
@ -184,12 +159,6 @@ class KeyRingTemplates {
* @param password passphrase for the private key. Can be null or blank for an unencrypted key.
* @return key ring
*/
fun modernKeyRing(userId: CharSequence?, password: String?): PGPSecretKeyRing =
password.let {
if (it.isNullOrBlank()) {
modernKeyRing(userId, Passphrase.emptyPassphrase())
} else {
modernKeyRing(userId, Passphrase.fromPassword(it))
}
}
fun modernKeyRing(userId: CharSequence?, password: CharSequence?): PGPSecretKeyRing =
modernKeyRing(userId, password.toPassphrase())
}

View File

@ -24,6 +24,7 @@ import org.pgpainless.bouncycastle.extensions.plusCertification
import org.pgpainless.implementation.ImplementationFactory
import org.pgpainless.key.generation.DefinePrimaryKey.PrimaryKeyBuilder
import org.pgpainless.key.generation.DefineSubkeys.SubkeyBuilder
import org.pgpainless.key.generation.OpenPgpKeyTemplates.Companion.v4
import org.pgpainless.key.generation.type.KeyType
import org.pgpainless.key.generation.type.eddsa.EdDSACurve
import org.pgpainless.key.generation.type.rsa.RsaLength
@ -37,8 +38,16 @@ import org.pgpainless.signature.builder.SubkeyBindingSignatureBuilder
import org.pgpainless.signature.subpackets.SelfSignatureSubpackets
import org.pgpainless.util.Passphrase
/**
* Function block that is applied to the OpenPGP [PrimaryKeyBuilder]. Within this block, you add
* User-IDs, User-Attributes and Direct-Key signatures on the primary key.
*/
typealias PrimaryKeyBlock = (PrimaryKeyBuilder.() -> Unit)
/**
* Function block that is applied to an OpenPGP [SubkeyBuilder]. Here you typically add
* subkey-binding signatures.
*/
typealias SubkeyBlock = (SubkeyBuilder.() -> Unit)
/**
@ -54,25 +63,20 @@ typealias SubkeyBlock = (SubkeyBuilder.() -> Unit)
* You can switch from the opinionated API to the unopinionated API by calling `unopinionated()` on
* the builder.
*/
class OpenPgpKeyGenerator internal constructor() {
class OpenPgpKeyGenerator(private val policy: Policy = PGPainless.getPolicy()) {
companion object {
/**
* Build a version 4 OpenPGP secret key.
*
* @param policy policy to ensure algorithm compliance and to determine default algorithms
* @param creationTime creation time for the secret key
* @param preferences suite of algorithm preferences and enabled features
*/
@JvmStatic
@JvmOverloads
fun buildV4Key(
policy: Policy = PGPainless.getPolicy(),
creationTime: Date = Date(),
preferences: AlgorithmSuite = policy.keyGenerationAlgorithmSuite
): OpinionatedDefinePrimaryKeyV4 {
return OpinionatedDefinePrimaryKeyV4(policy, creationTime, preferences)
}
/**
* Build a version 4 OpenPGP secret key.
*
* @param creationTime creation time for the secret key
* @param preferences suite of algorithm preferences and enabled features
*/
@JvmOverloads
fun buildV4Key(
creationTime: Date = Date(),
preferences: AlgorithmSuite = policy.keyGenerationAlgorithmSuite
): OpinionatedDefinePrimaryKeyV4 {
return OpinionatedDefinePrimaryKeyV4(policy, creationTime, preferences)
}
}
@ -211,6 +215,14 @@ internal constructor(val policy: Policy, val creationTime: Date, val preferences
// Do nothing
}
/**
* Return a [OpenPgpKeyTemplates] object which provides factory methods for generating OpenPGP
* keys from templates.
*
* @return templates
*/
abstract fun fromTemplate(): OpenPgpKeyTemplates
/**
* Function that can be applied to the primary key.
*
@ -1094,11 +1106,13 @@ class PrimaryKeyBuilderV4 internal constructor(keyPair: PGPKeyPair, builder: Def
this.keyPair
}
}
override fun fromTemplate(): OpenPgpKeyTemplates.V4 = v4()
}
}
/** Templates for OpenPGP key generation. */
class OpenPgpKeyTemplates private constructor() {
open class OpenPgpKeyTemplates private constructor() {
companion object {
@ -1111,7 +1125,7 @@ class OpenPgpKeyTemplates private constructor() {
}
/** Templates for version 4 OpenPGP keys. Version 4 keys are compliant to RFC4880. */
class V4 internal constructor() {
class V4 : OpenPgpKeyTemplates() {
/**
* Generate an OpenPGP key that consists of an Ed25519 primary key used for certification of
@ -1123,9 +1137,11 @@ class OpenPgpKeyTemplates private constructor() {
*/
fun ed25519Curve25519(
vararg userId: CharSequence,
creationTime: Date = Date()
creationTime: Date = Date(),
protector: SecretKeyRingProtector = SecretKeyRingProtector.unprotectedKeys()
): PGPSecretKeyRing =
OpenPgpKeyGenerator.buildV4Key(creationTime = creationTime)
OpenPgpKeyGenerator()
.buildV4Key(creationTime = creationTime)
.setPrimaryKey(KeyType.EDDSA(EdDSACurve._Ed25519)) {
// Add UserIDs
userId.forEachIndexed { index, uid ->
@ -1145,7 +1161,7 @@ class OpenPgpKeyTemplates private constructor() {
.addSigningSubkey(KeyType.EDDSA(EdDSACurve._Ed25519))
// encryption key
.addEncryptionSubkey(KeyType.XDH(XDHSpec._X25519))
.build()
.build(protector)
/**
* Generate an OpenPGP key that consists of an RSA primary key used for certification of
@ -1159,9 +1175,11 @@ class OpenPgpKeyTemplates private constructor() {
fun composedRsa(
vararg userId: CharSequence,
creationTime: Date = Date(),
length: RsaLength = RsaLength._4096
length: RsaLength = RsaLength._4096,
protector: SecretKeyRingProtector = SecretKeyRingProtector.unprotectedKeys()
): PGPSecretKeyRing =
OpenPgpKeyGenerator.buildV4Key(creationTime = creationTime)
OpenPgpKeyGenerator()
.buildV4Key(creationTime = creationTime)
.setPrimaryKey(KeyType.RSA(length)) {
// Add UserIDs
userId.forEachIndexed { index, uid ->
@ -1181,7 +1199,7 @@ class OpenPgpKeyTemplates private constructor() {
.addSigningSubkey(KeyType.RSA(length))
// encryption key
.addEncryptionSubkey(KeyType.RSA(length))
.build()
.build(protector)
/**
* Generate an OpenPGP key consisting of a single RSA key that is used for certification of
@ -1194,9 +1212,11 @@ class OpenPgpKeyTemplates private constructor() {
fun singleRsa(
vararg userId: CharSequence,
creationTime: Date = Date(),
length: RsaLength = RsaLength._4096
length: RsaLength = RsaLength._4096,
protector: SecretKeyRingProtector = SecretKeyRingProtector.unprotectedKeys()
): PGPSecretKeyRing =
OpenPgpKeyGenerator.buildV4Key(creationTime = creationTime)
OpenPgpKeyGenerator()
.buildV4Key(creationTime = creationTime)
.setPrimaryKey(KeyType.RSA(length)) {
userId.forEach { addUserId(it) }
addDirectKeySignature(
@ -1208,6 +1228,6 @@ class OpenPgpKeyTemplates private constructor() {
KeyFlag.ENCRYPT_STORAGE)
})
}
.build()
.build(protector)
}
}

View File

@ -11,7 +11,6 @@ import org.pgpainless.PGPainless
import org.pgpainless.algorithm.HashAlgorithm
import org.pgpainless.algorithm.KeyFlag
import org.pgpainless.bouncycastle.extensions.directKeySignatures
import org.pgpainless.key.generation.OpenPgpKeyGenerator
import org.pgpainless.key.generation.type.KeyType
import org.pgpainless.key.generation.type.eddsa.EdDSACurve
import org.pgpainless.key.generation.type.xdh.XDHSpec
@ -21,19 +20,26 @@ class KeyWithInacceptableSelfSignatureTest {
@Test
fun `key with inacceptable self-signature is not usable`() {
val genPolicy = Policy().apply {
certificationSignatureHashAlgorithmPolicy = Policy.HashAlgorithmPolicy(
HashAlgorithm.SHA1, listOf(HashAlgorithm.SHA1))
}
val genPolicy =
Policy().apply {
certificationSignatureHashAlgorithmPolicy =
Policy.HashAlgorithmPolicy(HashAlgorithm.SHA1, listOf(HashAlgorithm.SHA1))
}
val key = OpenPgpKeyGenerator.buildV4Key(genPolicy)
.setPrimaryKey(KeyType.EDDSA(EdDSACurve._Ed25519), listOf(KeyFlag.CERTIFY_OTHER, KeyFlag.SIGN_DATA))
.addEncryptionSubkey(KeyType.XDH(XDHSpec._X25519))
.build()
val key =
PGPainless.generateOpenPgpKey(genPolicy)
.buildV4Key()
.setPrimaryKey(
KeyType.EDDSA(EdDSACurve._Ed25519),
listOf(KeyFlag.CERTIFY_OTHER, KeyFlag.SIGN_DATA))
.addEncryptionSubkey(KeyType.XDH(XDHSpec._X25519))
.build()
assertEquals(HashAlgorithm.SHA1,
key.publicKey.directKeySignatures.single().hashAlgorithm
.let { HashAlgorithm.requireFromId(it) })
assertEquals(
HashAlgorithm.SHA1,
key.publicKey.directKeySignatures.single().hashAlgorithm.let {
HashAlgorithm.requireFromId(it)
})
val info = PGPainless.inspectKeyRing(key)
assertFalse(info.isUsableForSigning)

View File

@ -24,7 +24,8 @@ class MalformedKeyGenerationTest {
fun malformedPrimaryUserIdSubpacket() {
val userId = "Alice <alice@pgpainless.org>"
val key =
OpenPgpKeyGenerator.buildV4Key(Policy())
PGPainless.generateOpenPgpKey(Policy())
.buildV4Key()
.setPrimaryKey(KeyType.EDDSA(EdDSACurve._Ed25519)) {
addUserId(
userId,
@ -43,7 +44,8 @@ class MalformedKeyGenerationTest {
@Test
fun malformedExportableSubpacket() {
val key =
OpenPgpKeyGenerator.buildV4Key(Policy())
PGPainless.generateOpenPgpKey(Policy())
.buildV4Key()
.setPrimaryKey(KeyType.EDDSA(EdDSACurve._Ed25519)) {
addUserId(
"Alice <alice@pgpainless.org>",
@ -62,7 +64,8 @@ class MalformedKeyGenerationTest {
@Test
fun malformedRevocableSubpacket() {
val key =
OpenPgpKeyGenerator.buildV4Key(Policy())
PGPainless.generateOpenPgpKey(Policy())
.buildV4Key()
.setPrimaryKey(KeyType.EDDSA(EdDSACurve._Ed25519)) {
addUserId(
"Alice <alice@pgpainless.org>",
@ -82,7 +85,8 @@ class MalformedKeyGenerationTest {
fun primaryUserIdOnDirectKeySig() {
val policy = Policy()
val key =
OpenPgpKeyGenerator.buildV4Key(policy)
PGPainless.generateOpenPgpKey(policy)
.buildV4Key()
.setPrimaryKey(
KeyType.EDDSA(EdDSACurve._Ed25519),
listOf(KeyFlag.CERTIFY_OTHER, KeyFlag.SIGN_DATA)) {

View File

@ -38,7 +38,8 @@ class OpenPgpKeyGeneratorTest {
@Test
fun `minimal call with opinionated builder adds a default DK sig but no user info`() {
val key =
OpenPgpKeyGenerator.buildV4Key()
PGPainless.generateOpenPgpKey()
.buildV4Key()
.setPrimaryKey(KeyType.EDDSA(EdDSACurve._Ed25519))
.build()
@ -55,7 +56,8 @@ class OpenPgpKeyGeneratorTest {
@Test
fun `minimal call with unopinionated builder does not add a default DK sig`() {
val key =
OpenPgpKeyGenerator.buildV4Key()
PGPainless.generateOpenPgpKey()
.buildV4Key()
.unopinionated()
.setPrimaryKey(KeyType.EDDSA(EdDSACurve._Ed25519))
.build()
@ -68,7 +70,8 @@ class OpenPgpKeyGeneratorTest {
@Test
fun `adding a direct-key signature with the opinionated builder omits the default DK sig`() {
val key =
OpenPgpKeyGenerator.buildV4Key()
PGPainless.generateOpenPgpKey()
.buildV4Key()
.setPrimaryKey(KeyType.EDDSA(EdDSACurve._Ed25519)) {
addDirectKeySignature() // "overwrites" the default dk sig
}
@ -82,7 +85,8 @@ class OpenPgpKeyGeneratorTest {
@Test
fun `adding two user-ids will mark the first one as primary`() {
val key =
OpenPgpKeyGenerator.buildV4Key()
PGPainless.generateOpenPgpKey()
.buildV4Key()
.setPrimaryKey(KeyType.EDDSA(EdDSACurve._Ed25519)) {
addUserId("Primary <primary@example.com>")
addUserId("Non Primary <non-primary@example.com>")
@ -96,7 +100,8 @@ class OpenPgpKeyGeneratorTest {
@Test
fun `adding two user-ids but mark the first as non-primary will mark the second one as primary`() {
val key =
OpenPgpKeyGenerator.buildV4Key()
PGPainless.generateOpenPgpKey()
.buildV4Key()
.setPrimaryKey(KeyType.EDDSA(EdDSACurve._Ed25519)) {
addUserId(
"Non Primary <non-primary@example.com>",
@ -126,7 +131,8 @@ class OpenPgpKeyGeneratorTest {
.generate()
val key =
OpenPgpKeyGenerator.buildV4Key(policy)
PGPainless.generateOpenPgpKey(policy)
.buildV4Key()
.setPrimaryKey(KeyType.EDDSA(EdDSACurve._Ed25519)) {
addUserAttribute(attr1) // primary, since it is the first
addUserAttribute(attr2) // non-primary
@ -160,7 +166,8 @@ class OpenPgpKeyGeneratorTest {
.generate()
val key =
OpenPgpKeyGenerator.buildV4Key(policy)
PGPainless.generateOpenPgpKey(policy)
.buildV4Key()
.setPrimaryKey(KeyType.EDDSA(EdDSACurve._Ed25519)) {
addUserId(userId)
addUserAttribute(userAttribute)
@ -186,7 +193,8 @@ class OpenPgpKeyGeneratorTest {
@Test
fun `adding signing key will add embedded back-signature`() {
val key =
OpenPgpKeyGenerator.buildV4Key()
PGPainless.generateOpenPgpKey()
.buildV4Key()
.setPrimaryKey(KeyType.EDDSA(EdDSACurve._Ed25519))
.addSubkey(KeyType.EDDSA(EdDSACurve._Ed25519), listOf(KeyFlag.SIGN_DATA))
.build()
@ -205,7 +213,8 @@ class OpenPgpKeyGeneratorTest {
@Test
fun testUnopinionatedV4() {
// Unopinionated
OpenPgpKeyGenerator.buildV4Key()
PGPainless.generateOpenPgpKey()
.buildV4Key()
.unopinionated()
.setPrimaryKey(KeyType.EDDSA(EdDSACurve._Ed25519)) {
addDirectKeySignature()
@ -221,7 +230,8 @@ class OpenPgpKeyGeneratorTest {
fun testOpinionatedV4() {
// Opinionated
val time = DateUtil.parseUTCDate("2024-01-01 00:00:00 UTC")
OpenPgpKeyGenerator.buildV4Key(creationTime = time)
PGPainless.generateOpenPgpKey()
.buildV4Key(creationTime = time)
.setPrimaryKey(KeyType.EDDSA(EdDSACurve._Ed25519), listOf(KeyFlag.CERTIFY_OTHER)) {
addUserId("Alice <alice@pgpainless.org>")
}
@ -254,7 +264,8 @@ class OpenPgpKeyGeneratorTest {
@Test
fun test() {
OpenPgpKeyGenerator.buildV4Key()
PGPainless.generateOpenPgpKey()
.buildV4Key()
.setPrimaryKey(KeyType.RSA(RsaLength._3072), keyFlags = listOf(KeyFlag.CERTIFY_OTHER))
.build()
.toAsciiArmor()
@ -268,7 +279,8 @@ class OpenPgpKeyGeneratorTest {
Policy.PublicKeyAlgorithmPolicy(buildMap { put(PublicKeyAlgorithm.RSA_GENERAL, 3072) })
assertThrows<IllegalArgumentException> {
OpenPgpKeyGenerator.buildV4Key(policy)
PGPainless.generateOpenPgpKey(policy)
.buildV4Key()
// opinionated builder verifies PK parameters
.setPrimaryKey(KeyType.RSA(RsaLength._2048)) // too weak
}
@ -280,7 +292,8 @@ class OpenPgpKeyGeneratorTest {
policy.publicKeyAlgorithmPolicy =
Policy.PublicKeyAlgorithmPolicy(buildMap { put(PublicKeyAlgorithm.RSA_GENERAL, 3072) })
OpenPgpKeyGenerator.buildV4Key(policy)
PGPainless.generateOpenPgpKey(policy)
.buildV4Key()
.unopinionated() // unopinionated builder allows for non-compliant configurations
.setPrimaryKey(KeyType.RSA(RsaLength._2048))
}
@ -288,7 +301,8 @@ class OpenPgpKeyGeneratorTest {
@Test
fun `skip default DirectKey signature will not add one`() {
val key =
OpenPgpKeyGenerator.buildV4Key()
PGPainless.generateOpenPgpKey()
.buildV4Key()
.setPrimaryKey(KeyType.EDDSA(EdDSACurve._Ed25519)) { skipDefaultSignature() }
.build()
@ -304,7 +318,7 @@ class OpenPgpKeyGeneratorTest {
fun `opinionated add UserID with weak hash algorithm fails`() {
val policy = Policy()
assertThrows<IllegalArgumentException> {
OpenPgpKeyGenerator.buildV4Key(policy).setPrimaryKey(
PGPainless.generateOpenPgpKey(policy).buildV4Key().setPrimaryKey(
KeyType.EDDSA(EdDSACurve._Ed25519)) {
addUserId("Alice <alice@example.org>", hashAlgorithm = HashAlgorithm.SHA1)
}
@ -314,7 +328,7 @@ class OpenPgpKeyGeneratorTest {
@Test
fun `unopinionated add UserID with weak hash algorithm is okay`() {
val policy = Policy()
OpenPgpKeyGenerator.buildV4Key(policy).unopinionated().setPrimaryKey(
PGPainless.generateOpenPgpKey(policy).buildV4Key().unopinionated().setPrimaryKey(
KeyType.EDDSA(EdDSACurve._Ed25519)) {
addUserId("Alice <alice@example.org>", hashAlgorithm = HashAlgorithm.SHA1)
}
@ -324,7 +338,7 @@ class OpenPgpKeyGeneratorTest {
fun `opinionated add UserAttribute with weak hash algorithm fails`() {
val policy = Policy()
assertThrows<IllegalArgumentException> {
OpenPgpKeyGenerator.buildV4Key(policy).setPrimaryKey(
PGPainless.generateOpenPgpKey(policy).buildV4Key().setPrimaryKey(
KeyType.EDDSA(EdDSACurve._Ed25519)) {
addUserAttribute(
PGPUserAttributeSubpacketVectorGenerator().generate(),
@ -336,7 +350,7 @@ class OpenPgpKeyGeneratorTest {
@Test
fun `unopinionated add UserAttribute with weak hash algorithm is okay`() {
val policy = Policy()
OpenPgpKeyGenerator.buildV4Key(policy).unopinionated().setPrimaryKey(
PGPainless.generateOpenPgpKey(policy).buildV4Key().unopinionated().setPrimaryKey(
KeyType.EDDSA(EdDSACurve._Ed25519)) {
addUserAttribute(
PGPUserAttributeSubpacketVectorGenerator().generate(),
@ -348,7 +362,7 @@ class OpenPgpKeyGeneratorTest {
fun `opinionated add DK sig with weak hash algorithm fails`() {
val policy = Policy()
assertThrows<IllegalArgumentException> {
OpenPgpKeyGenerator.buildV4Key(policy).setPrimaryKey(
PGPainless.generateOpenPgpKey(policy).buildV4Key().setPrimaryKey(
KeyType.EDDSA(EdDSACurve._Ed25519)) {
addDirectKeySignature(hashAlgorithm = HashAlgorithm.SHA1)
}
@ -358,7 +372,7 @@ class OpenPgpKeyGeneratorTest {
@Test
fun `unopinionated add DK sig with weak hash algorithm is okay`() {
val policy = Policy()
OpenPgpKeyGenerator.buildV4Key(policy).unopinionated().setPrimaryKey(
PGPainless.generateOpenPgpKey(policy).buildV4Key().unopinionated().setPrimaryKey(
KeyType.EDDSA(EdDSACurve._Ed25519)) {
addDirectKeySignature(hashAlgorithm = HashAlgorithm.SHA1)
}
@ -371,7 +385,7 @@ class OpenPgpKeyGeneratorTest {
val t1 = DateUtil.parseUTCDate("2024-02-01 00:00:00 UTC")
assertThrows<IllegalArgumentException> {
OpenPgpKeyGenerator.buildV4Key(policy, t1).setPrimaryKey(
PGPainless.generateOpenPgpKey(policy).buildV4Key(t1).setPrimaryKey(
KeyType.EDDSA(EdDSACurve._Ed25519)) {
addUserId("Alice <alice@example.org>", bindingTime = t0)
}
@ -384,7 +398,7 @@ class OpenPgpKeyGeneratorTest {
val t0 = DateUtil.parseUTCDate("2024-01-01 00:00:00 UTC")
val t1 = DateUtil.parseUTCDate("2024-02-01 00:00:00 UTC")
OpenPgpKeyGenerator.buildV4Key(policy, t1).unopinionated().setPrimaryKey(
PGPainless.generateOpenPgpKey(policy).buildV4Key(t1).unopinionated().setPrimaryKey(
KeyType.EDDSA(EdDSACurve._Ed25519)) {
addUserId("Alice <alice@example.org>", bindingTime = t0)
}
@ -397,7 +411,7 @@ class OpenPgpKeyGeneratorTest {
val t1 = DateUtil.parseUTCDate("2024-02-01 00:00:00 UTC")
assertThrows<IllegalArgumentException> {
OpenPgpKeyGenerator.buildV4Key(policy, t1).setPrimaryKey(
PGPainless.generateOpenPgpKey(policy).buildV4Key(t1).setPrimaryKey(
KeyType.EDDSA(EdDSACurve._Ed25519)) {
addUserAttribute(
PGPUserAttributeSubpacketVectorGenerator().generate(), bindingTime = t0)
@ -411,7 +425,7 @@ class OpenPgpKeyGeneratorTest {
val t0 = DateUtil.parseUTCDate("2024-01-01 00:00:00 UTC")
val t1 = DateUtil.parseUTCDate("2024-02-01 00:00:00 UTC")
OpenPgpKeyGenerator.buildV4Key(policy, t1).unopinionated().setPrimaryKey(
PGPainless.generateOpenPgpKey(policy).buildV4Key(t1).unopinionated().setPrimaryKey(
KeyType.EDDSA(EdDSACurve._Ed25519)) {
addUserAttribute(
PGPUserAttributeSubpacketVectorGenerator().generate(), bindingTime = t0)
@ -425,7 +439,7 @@ class OpenPgpKeyGeneratorTest {
val t1 = DateUtil.parseUTCDate("2024-02-01 00:00:00 UTC")
assertThrows<IllegalArgumentException> {
OpenPgpKeyGenerator.buildV4Key(policy, t1).setPrimaryKey(
PGPainless.generateOpenPgpKey(policy).buildV4Key(t1).setPrimaryKey(
KeyType.EDDSA(EdDSACurve._Ed25519)) {
addDirectKeySignature(bindingTime = t0)
}
@ -438,7 +452,7 @@ class OpenPgpKeyGeneratorTest {
val t0 = DateUtil.parseUTCDate("2024-01-01 00:00:00 UTC")
val t1 = DateUtil.parseUTCDate("2024-02-01 00:00:00 UTC")
OpenPgpKeyGenerator.buildV4Key(policy, t1).unopinionated().setPrimaryKey(
PGPainless.generateOpenPgpKey(policy).buildV4Key(t1).unopinionated().setPrimaryKey(
KeyType.EDDSA(EdDSACurve._Ed25519)) {
addDirectKeySignature(bindingTime = t0)
}
@ -451,7 +465,8 @@ class OpenPgpKeyGeneratorTest {
val t1 = DateUtil.parseUTCDate("2024-02-01 00:00:00 UTC")
assertThrows<IllegalArgumentException> {
OpenPgpKeyGenerator.buildV4Key(policy, t1)
PGPainless.generateOpenPgpKey(policy)
.buildV4Key(t1)
.setPrimaryKey(KeyType.EDDSA(EdDSACurve._Ed25519))
.addSubkey(KeyType.XDH(XDHSpec._X25519), null, t0)
}
@ -463,7 +478,8 @@ class OpenPgpKeyGeneratorTest {
val t0 = DateUtil.parseUTCDate("2024-01-01 00:00:00 UTC")
val t1 = DateUtil.parseUTCDate("2024-02-01 00:00:00 UTC")
OpenPgpKeyGenerator.buildV4Key(policy, t1)
PGPainless.generateOpenPgpKey(policy)
.buildV4Key(t1)
.unopinionated()
.setPrimaryKey(KeyType.EDDSA(EdDSACurve._Ed25519))
.addSubkey(KeyType.XDH(XDHSpec._X25519), null, t0)
@ -476,7 +492,8 @@ class OpenPgpKeyGeneratorTest {
val t1 = DateUtil.parseUTCDate("2024-02-01 00:00:00 UTC")
assertThrows<IllegalArgumentException> {
OpenPgpKeyGenerator.buildV4Key(policy, t1)
PGPainless.generateOpenPgpKey(policy)
.buildV4Key(t1)
.setPrimaryKey(KeyType.EDDSA(EdDSACurve._Ed25519))
.addSubkey(KeyType.XDH(XDHSpec._X25519)) { addBindingSignature(bindingTime = t0) }
}
@ -488,7 +505,8 @@ class OpenPgpKeyGeneratorTest {
val t0 = DateUtil.parseUTCDate("2024-01-01 00:00:00 UTC")
val t1 = DateUtil.parseUTCDate("2024-02-01 00:00:00 UTC")
OpenPgpKeyGenerator.buildV4Key(policy, t1)
PGPainless.generateOpenPgpKey(policy)
.buildV4Key(t1)
.unopinionated()
.setPrimaryKey(KeyType.EDDSA(EdDSACurve._Ed25519))
.addSubkey(KeyType.XDH(XDHSpec._X25519)) { addBindingSignature(bindingTime = t0) }
@ -499,7 +517,8 @@ class OpenPgpKeyGeneratorTest {
val policy = Policy()
assertThrows<IllegalArgumentException> {
OpenPgpKeyGenerator.buildV4Key(policy)
PGPainless.generateOpenPgpKey(policy)
.buildV4Key()
.setPrimaryKey(KeyType.EDDSA(EdDSACurve._Ed25519))
.addSubkey(KeyType.XDH(XDHSpec._X25519)) {
addBindingSignature(hashAlgorithm = HashAlgorithm.SHA1)
@ -511,7 +530,8 @@ class OpenPgpKeyGeneratorTest {
fun `unopinionated add subkey with weak binding signature hash algorithm is okay`() {
val policy = Policy()
OpenPgpKeyGenerator.buildV4Key(policy)
PGPainless.generateOpenPgpKey(policy)
.buildV4Key()
.setPrimaryKey(KeyType.EDDSA(EdDSACurve._Ed25519))
.unopinionated()
.addSubkey(KeyType.XDH(XDHSpec._X25519)) {
@ -524,7 +544,9 @@ class OpenPgpKeyGeneratorTest {
val policy = Policy()
assertThrows<IllegalArgumentException> {
OpenPgpKeyGenerator.buildV4Key(policy).setPrimaryKey(KeyType.XDH(XDHSpec._X25519))
PGPainless.generateOpenPgpKey(policy)
.buildV4Key()
.setPrimaryKey(KeyType.XDH(XDHSpec._X25519))
}
}
@ -533,7 +555,8 @@ class OpenPgpKeyGeneratorTest {
val policy = Policy()
assertThrows<IllegalArgumentException> {
OpenPgpKeyGenerator.buildV4Key(policy)
PGPainless.generateOpenPgpKey(policy)
.buildV4Key()
.setPrimaryKey(
KeyType.EDDSA(EdDSACurve._Ed25519),
listOf(KeyFlag.CERTIFY_OTHER, KeyFlag.ENCRYPT_STORAGE))
@ -544,7 +567,8 @@ class OpenPgpKeyGeneratorTest {
fun `unopinionated set primary key to sign-only algorithm but with encryption flag is okay`() {
val policy = Policy()
OpenPgpKeyGenerator.buildV4Key(policy)
PGPainless.generateOpenPgpKey(policy)
.buildV4Key()
.unopinionated()
.setPrimaryKey(
KeyType.EDDSA(EdDSACurve._Ed25519),
@ -556,7 +580,8 @@ class OpenPgpKeyGeneratorTest {
val policy = Policy()
val key =
OpenPgpKeyGenerator.buildV4Key(policy)
PGPainless.generateOpenPgpKey(policy)
.buildV4Key()
.setPrimaryKey(KeyType.EDDSA(EdDSACurve._Ed25519), keyFlags = null)
.build()
@ -568,7 +593,8 @@ class OpenPgpKeyGeneratorTest {
val policy = Policy()
assertThrows<IllegalArgumentException> {
OpenPgpKeyGenerator.buildV4Key(policy)
PGPainless.generateOpenPgpKey(policy)
.buildV4Key()
.setPrimaryKey(KeyType.EDDSA(EdDSACurve._Ed25519))
.addSubkey(
KeyType.XDH(XDHSpec._X25519), listOf(KeyFlag.ENCRYPT_COMMS, KeyFlag.SIGN_DATA))
@ -580,7 +606,8 @@ class OpenPgpKeyGeneratorTest {
val policy = Policy()
assertThrows<IllegalArgumentException> {
OpenPgpKeyGenerator.buildV4Key(policy)
PGPainless.generateOpenPgpKey(policy)
.buildV4Key()
.setPrimaryKey(KeyType.EDDSA(EdDSACurve._Ed25519))
.addSubkey(
KeyType.EDDSA(EdDSACurve._Ed25519),
@ -592,7 +619,8 @@ class OpenPgpKeyGeneratorTest {
fun `unopinionated add sign-only sukey but with additional encryption flag is okay`() {
val policy = Policy()
OpenPgpKeyGenerator.buildV4Key(policy)
PGPainless.generateOpenPgpKey(policy)
.buildV4Key()
.setPrimaryKey(KeyType.EDDSA(EdDSACurve._Ed25519))
.unopinionated()
.addSubkey(
@ -609,7 +637,8 @@ class OpenPgpKeyGeneratorTest {
"ffd8ffe000104a46494600010101004800480000ffdb004300030202020202030202020303030304060404040404080606050609080a0a090809090a0c0f0c0a0b0e0b09090d110d0e0f101011100a0c12131210130f101010ffc9000b080001000101011100ffcc000600101005ffda0008010100003f00d2cf20ffd9")
val key =
OpenPgpKeyGenerator.buildV4Key()
PGPainless.generateOpenPgpKey()
.buildV4Key()
.setPrimaryKey(KeyType.EDDSA(EdDSACurve._Ed25519)) {
addImageAttribute(jpegBytes.inputStream())
}
@ -622,7 +651,8 @@ class OpenPgpKeyGeneratorTest {
fun `generate key with expiration time`() {
val policy = Policy()
OpenPgpKeyGenerator.buildV4Key(policy)
PGPainless.generateOpenPgpKey(policy)
.buildV4Key()
.setPrimaryKey(KeyType.EDDSA(EdDSACurve._Ed25519)) {
addDirectKeySignature(
SelfSignatureSubpackets.applyHashed {