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(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),
;

View File

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

View File

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

View File

@ -5,8 +5,6 @@
package org.pgpainless.key.generation
import java.io.InputStream
import org.bouncycastle.bcpg.attr.ImageAttribute
import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVectorGenerator
import org.bouncycastle.util.io.Streams
import org.junit.jupiter.api.Assertions.assertArrayEquals
import org.junit.jupiter.api.Assertions.assertTrue
@ -33,11 +31,6 @@ class GenerateOpenPgpKeyTest {
GenerateOpenPgpKey(Policy.getInstance(), date)
.buildV4Key(KeyType.EDDSA(EdDSACurve._Ed25519), listOf(KeyFlag.CERTIFY_OTHER))
.addUserId("Alice")
.addUserAttribute(
PGPUserAttributeSubpacketVectorGenerator()
.apply { setImageAttribute(ImageAttribute.JPEG, byteArrayOf()) }
.generate(),
)
.addEncryptionSubkey(KeyType.XDH(XDHSpec._X25519))
.addSigningSubkey(KeyType.EDDSA(EdDSACurve._Ed25519))
.build(SecretKeyRingProtector.unprotectedKeys())
@ -63,6 +56,35 @@ class GenerateOpenPgpKeyTest {
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
fun testKeyGenerationWithUnacceptablePKAlgorithmFails() {
// Policy only allows RSA 4096 algorithms