Enforce key capabilities for primary key and add tests

This commit is contained in:
Paul Schaub 2024-02-02 18:18:28 +01:00
parent 378890f83a
commit f384ce84be
Signed by: vanitasvitae
GPG Key ID: 62BEE9264BF17311
4 changed files with 47 additions and 34 deletions

View File

@ -59,10 +59,7 @@ enum class PublicKeyAlgorithm(
/** Diffie-Hellman key exchange algorithm. */ /** Diffie-Hellman key exchange algorithm. */
DIFFIE_HELLMAN(21, false, true), DIFFIE_HELLMAN(21, false, true),
/** /** Digital Signature Algorithm based on twisted Edwards Curves. For OpenPGP v4 only. */
* Digital Signature Algorithm based on twisted Edwards Curves.
* For OpenPGP v4 only.
*/
EDDSA(22, true, false), EDDSA(22, true, false),
; ;

View File

@ -35,9 +35,7 @@ open class GenerateOpenPgpKey(
private val preferences: AlgorithmSuite = policy.keyGenerationAlgorithmSuite private val preferences: AlgorithmSuite = policy.keyGenerationAlgorithmSuite
) { ) {
/** /** Builder for OpenPGP secret keys. */
* Builder for OpenPGP secret keys.
*/
abstract class OpenPgpKeyBuilder( abstract class OpenPgpKeyBuilder(
protected val policy: Policy, protected val policy: Policy,
protected val referenceTime: Date, protected val referenceTime: Date,
@ -96,6 +94,9 @@ open class GenerateOpenPgpKey(
) : OpenPgpKeyBuilder(policy, referenceTime, preferences) { ) : OpenPgpKeyBuilder(policy, referenceTime, preferences) {
init { init {
require(primaryKeyType.canCertify) {
"KeyType $primaryKeyType MUST be certification capable."
}
sanitizePublicKeyAlgorithms(primaryKeyType, policy) sanitizePublicKeyAlgorithms(primaryKeyType, policy)
} }
@ -203,9 +204,8 @@ open class GenerateOpenPgpKey(
} }
/** /**
* Add a new subkey to be used for encryption. * Add a new subkey to be used for encryption. The binding signature will mark the key as
* The binding signature will mark the key as encryption-capable using both * encryption-capable using both [KeyFlag.ENCRYPT_COMMS] and [KeyFlag.ENCRYPT_STORAGE].
* [KeyFlag.ENCRYPT_COMMS] and [KeyFlag.ENCRYPT_STORAGE].
* *
* @param keyType type of the encryption subkey * @param keyType type of the encryption subkey
* @param creationTime time of creation of the subkey * @param creationTime time of creation of the subkey
@ -224,13 +224,12 @@ open class GenerateOpenPgpKey(
keyType, keyType,
creationTime, creationTime,
bindingTime, bindingTime,
listOf(KeyFlag.ENCRYPT_STORAGE, KeyFlag.ENCRYPT_COMMS) listOf(KeyFlag.ENCRYPT_STORAGE, KeyFlag.ENCRYPT_COMMS))
)
} }
/** /**
* Add a new subkey to be used for creating data signatures. * Add a new subkey to be used for creating data signatures. The binding signature will mark
* The binding signature will mark the key as signing-capable using [KeyFlag.SIGN_DATA]. * the key as signing-capable using [KeyFlag.SIGN_DATA].
* *
* @param keyType type of the signing subkey * @param keyType type of the signing subkey
* @param creationTime time of creation of the subkey * @param creationTime time of creation of the subkey
@ -242,20 +241,17 @@ open class GenerateOpenPgpKey(
creationTime: Date = referenceTime, creationTime: Date = referenceTime,
bindingTime: Date = creationTime bindingTime: Date = creationTime
) = apply { ) = apply {
require(keyType.canSign) { require(keyType.canSign) { "KeyType $keyType cannot be used for signing keys." }
"KeyType $keyType cannot be used for signing keys."
}
addSubkey(keyType, creationTime, bindingTime, listOf(KeyFlag.SIGN_DATA)) addSubkey(keyType, creationTime, bindingTime, listOf(KeyFlag.SIGN_DATA))
} }
/** /**
* Build the finished OpenPGP key. * Build the finished OpenPGP key. By default, the key will not be protected using
* By default, the key will not be protected using passphrases. * passphrases. To set a passphrase, you can provide
* To set a passphrase, you can provide [SecretKeyRingProtector.unlockAnyKeyWith] with * [SecretKeyRingProtector.unlockAnyKeyWith] with a passphrase of your choice.
* a passphrase of your choice.
* *
* @param protector protector to secure the secret keys using passphrases. * @param protector protector to secure the secret keys using passphrases. Defaults to
* Defaults to [SecretKeyRingProtector.unprotectedKeys]. * [SecretKeyRingProtector.unprotectedKeys].
* @return OpenPGP Secret Key * @return OpenPGP Secret Key
*/ */
fun build( fun build(

View File

@ -37,10 +37,8 @@ class OpenPgpComponentKeyBuilder {
internal var key = generateKeyPair() internal var key = generateKeyPair()
fun subkey( fun subkey(type: KeyType, creationTime: Date = certificateCreationTime): V4SubkeyBuilder =
type: KeyType, V4SubkeyBuilder(type, creationTime, policy, primaryKey())
creationTime: Date = certificateCreationTime
): V4SubkeyBuilder = V4SubkeyBuilder(type, creationTime, policy, primaryKey())
internal abstract fun primaryKey(): V4PrimaryKeyBuilder internal abstract fun primaryKey(): V4PrimaryKeyBuilder

View File

@ -5,8 +5,6 @@
package org.pgpainless.key.generation package org.pgpainless.key.generation
import java.io.InputStream import java.io.InputStream
import org.bouncycastle.bcpg.attr.ImageAttribute
import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVectorGenerator
import org.bouncycastle.util.io.Streams import org.bouncycastle.util.io.Streams
import org.junit.jupiter.api.Assertions.assertArrayEquals import org.junit.jupiter.api.Assertions.assertArrayEquals
import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Assertions.assertTrue
@ -33,11 +31,6 @@ class GenerateOpenPgpKeyTest {
GenerateOpenPgpKey(Policy.getInstance(), date) GenerateOpenPgpKey(Policy.getInstance(), date)
.buildV4Key(KeyType.EDDSA(EdDSACurve._Ed25519), listOf(KeyFlag.CERTIFY_OTHER)) .buildV4Key(KeyType.EDDSA(EdDSACurve._Ed25519), listOf(KeyFlag.CERTIFY_OTHER))
.addUserId("Alice") .addUserId("Alice")
.addUserAttribute(
PGPUserAttributeSubpacketVectorGenerator()
.apply { setImageAttribute(ImageAttribute.JPEG, byteArrayOf()) }
.generate(),
)
.addEncryptionSubkey(KeyType.XDH(XDHSpec._X25519)) .addEncryptionSubkey(KeyType.XDH(XDHSpec._X25519))
.addSigningSubkey(KeyType.EDDSA(EdDSACurve._Ed25519)) .addSigningSubkey(KeyType.EDDSA(EdDSACurve._Ed25519))
.build(SecretKeyRingProtector.unprotectedKeys()) .build(SecretKeyRingProtector.unprotectedKeys())
@ -63,6 +56,35 @@ class GenerateOpenPgpKeyTest {
println(PGPainless.asciiArmor(key)) println(PGPainless.asciiArmor(key))
} }
@Test
fun primaryKeyMustBeCertificationCapable() {
assertThrows<IllegalArgumentException> {
GenerateOpenPgpKey(Policy.getInstance())
.buildV4Key(
KeyType.XDH(XDHSpec._X25519)) // XDH is not signing/certification capable
}
}
@Test
fun encryptionSubkeyMustBeEncryptionCapable() {
val builder =
GenerateOpenPgpKey(Policy.getInstance()).buildV4Key(KeyType.EDDSA(EdDSACurve._Ed25519))
assertThrows<IllegalArgumentException> {
builder.addEncryptionSubkey(KeyType.EDDSA(EdDSACurve._Ed25519))
}
}
@Test
fun signingSubkeysMustBeSigningCapable() {
val builder =
GenerateOpenPgpKey(Policy.getInstance()).buildV4Key(KeyType.EDDSA(EdDSACurve._Ed25519))
assertThrows<IllegalArgumentException> {
builder.addSigningSubkey(KeyType.XDH(XDHSpec._X25519))
}
}
@Test @Test
fun testKeyGenerationWithUnacceptablePKAlgorithmFails() { fun testKeyGenerationWithUnacceptablePKAlgorithmFails() {
// Policy only allows RSA 4096 algorithms // Policy only allows RSA 4096 algorithms