mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-12-25 12:27:58 +01:00
Further progress
This commit is contained in:
parent
6416ef1e07
commit
02f6e37c4f
3 changed files with 96 additions and 92 deletions
|
@ -68,11 +68,8 @@ class BaseOpenPgpKeyBuilder {
|
||||||
class BaseV4PrimaryKeyBuilder(type: KeyType, creationTime: Date, policy: Policy) :
|
class BaseV4PrimaryKeyBuilder(type: KeyType, creationTime: Date, policy: Policy) :
|
||||||
BaseV4KeyBuilder<BaseV4PrimaryKeyBuilder>(type, creationTime, policy = policy) {
|
BaseV4KeyBuilder<BaseV4PrimaryKeyBuilder>(type, creationTime, policy = policy) {
|
||||||
|
|
||||||
internal fun isWithoutUserIds() = !key.publicKey.userIDs.hasNext()
|
|
||||||
|
|
||||||
fun userId(
|
fun userId(
|
||||||
userId: CharSequence,
|
userId: CharSequence,
|
||||||
algorithmSuite: AlgorithmSuite = policy.keyGenerationAlgorithmSuite,
|
|
||||||
certificationType: CertificationType = CertificationType.POSITIVE,
|
certificationType: CertificationType = CertificationType.POSITIVE,
|
||||||
bindingTime: Date = creationTime,
|
bindingTime: Date = creationTime,
|
||||||
hashAlgorithm: HashAlgorithm =
|
hashAlgorithm: HashAlgorithm =
|
||||||
|
@ -81,12 +78,7 @@ class BaseOpenPgpKeyBuilder {
|
||||||
) = apply {
|
) = apply {
|
||||||
val sig =
|
val sig =
|
||||||
buildCertificationFor(
|
buildCertificationFor(
|
||||||
userId,
|
userId, certificationType, bindingTime, hashAlgorithm, subpacketsCallback)
|
||||||
algorithmSuite,
|
|
||||||
certificationType,
|
|
||||||
bindingTime,
|
|
||||||
hashAlgorithm,
|
|
||||||
subpacketsCallback)
|
|
||||||
key =
|
key =
|
||||||
PGPKeyPair(
|
PGPKeyPair(
|
||||||
PGPPublicKey.addCertification(key.publicKey, userId.toString(), sig),
|
PGPPublicKey.addCertification(key.publicKey, userId.toString(), sig),
|
||||||
|
@ -95,7 +87,6 @@ class BaseOpenPgpKeyBuilder {
|
||||||
|
|
||||||
fun buildCertificationFor(
|
fun buildCertificationFor(
|
||||||
userId: CharSequence,
|
userId: CharSequence,
|
||||||
algorithmSuite: AlgorithmSuite,
|
|
||||||
certificationType: CertificationType,
|
certificationType: CertificationType,
|
||||||
bindingTime: Date,
|
bindingTime: Date,
|
||||||
hashAlgorithm: HashAlgorithm,
|
hashAlgorithm: HashAlgorithm,
|
||||||
|
@ -104,19 +95,13 @@ class BaseOpenPgpKeyBuilder {
|
||||||
val builder =
|
val builder =
|
||||||
SelfSignatureBuilder(
|
SelfSignatureBuilder(
|
||||||
key.privateKey, key.publicKey, certificationType.signatureType, hashAlgorithm)
|
key.privateKey, key.publicKey, certificationType.signatureType, hashAlgorithm)
|
||||||
builder.hashedSubpackets.apply {
|
builder.hashedSubpackets.apply { setSignatureCreationTime(bindingTime) }
|
||||||
setSignatureCreationTime(bindingTime)
|
|
||||||
setPreferredHashAlgorithms(algorithmSuite.hashAlgorithms)
|
|
||||||
setPreferredSymmetricKeyAlgorithms(algorithmSuite.symmetricKeyAlgorithms)
|
|
||||||
setPreferredCompressionAlgorithms(algorithmSuite.compressionAlgorithms)
|
|
||||||
}
|
|
||||||
builder.applyCallback(subpacketsCallback)
|
builder.applyCallback(subpacketsCallback)
|
||||||
return builder.build(userId)
|
return builder.build(userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun userAttribute(
|
fun userAttribute(
|
||||||
userAttribute: PGPUserAttributeSubpacketVector,
|
userAttribute: PGPUserAttributeSubpacketVector,
|
||||||
algorithmSuite: AlgorithmSuite = policy.keyGenerationAlgorithmSuite,
|
|
||||||
certificationType: CertificationType = CertificationType.POSITIVE,
|
certificationType: CertificationType = CertificationType.POSITIVE,
|
||||||
bindingTime: Date = creationTime,
|
bindingTime: Date = creationTime,
|
||||||
hashAlgorithm: HashAlgorithm =
|
hashAlgorithm: HashAlgorithm =
|
||||||
|
@ -126,7 +111,6 @@ class BaseOpenPgpKeyBuilder {
|
||||||
val sig =
|
val sig =
|
||||||
buildCertificationFor(
|
buildCertificationFor(
|
||||||
userAttribute,
|
userAttribute,
|
||||||
algorithmSuite,
|
|
||||||
certificationType,
|
certificationType,
|
||||||
bindingTime,
|
bindingTime,
|
||||||
hashAlgorithm,
|
hashAlgorithm,
|
||||||
|
@ -139,7 +123,6 @@ class BaseOpenPgpKeyBuilder {
|
||||||
|
|
||||||
fun buildCertificationFor(
|
fun buildCertificationFor(
|
||||||
userAttribute: PGPUserAttributeSubpacketVector,
|
userAttribute: PGPUserAttributeSubpacketVector,
|
||||||
algorithmSuite: AlgorithmSuite,
|
|
||||||
certificationType: CertificationType,
|
certificationType: CertificationType,
|
||||||
bindingTime: Date,
|
bindingTime: Date,
|
||||||
hashAlgorithm: HashAlgorithm,
|
hashAlgorithm: HashAlgorithm,
|
||||||
|
@ -148,12 +131,7 @@ class BaseOpenPgpKeyBuilder {
|
||||||
val builder =
|
val builder =
|
||||||
SelfSignatureBuilder(
|
SelfSignatureBuilder(
|
||||||
key.privateKey, key.publicKey, certificationType.signatureType, hashAlgorithm)
|
key.privateKey, key.publicKey, certificationType.signatureType, hashAlgorithm)
|
||||||
builder.hashedSubpackets.apply {
|
builder.hashedSubpackets.apply { setSignatureCreationTime(bindingTime) }
|
||||||
setSignatureCreationTime(bindingTime)
|
|
||||||
setPreferredHashAlgorithms(algorithmSuite.hashAlgorithms)
|
|
||||||
setPreferredSymmetricKeyAlgorithms(algorithmSuite.symmetricKeyAlgorithms)
|
|
||||||
setPreferredCompressionAlgorithms(algorithmSuite.compressionAlgorithms)
|
|
||||||
}
|
|
||||||
builder.applyCallback(subpacketsCallback)
|
builder.applyCallback(subpacketsCallback)
|
||||||
return builder.build(userAttribute)
|
return builder.build(userAttribute)
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,79 +17,130 @@ import org.pgpainless.key.protection.SecretKeyRingProtector
|
||||||
import org.pgpainless.policy.Policy
|
import org.pgpainless.policy.Policy
|
||||||
import org.pgpainless.signature.subpackets.SelfSignatureSubpackets
|
import org.pgpainless.signature.subpackets.SelfSignatureSubpackets
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OpenPGP key builder. This implementation supersedes the old [KeyRingBuilder].
|
||||||
|
*
|
||||||
|
* @param policy algorithm policy, which is consulted to determine suitable algorithms
|
||||||
|
* @param referenceTime reference time for key generation
|
||||||
|
* @param preferences set of preferred algorithms and enabled features
|
||||||
|
*/
|
||||||
open class OpenPgpKeyBuilder(
|
open class OpenPgpKeyBuilder(
|
||||||
protected val policy: Policy,
|
protected val policy: Policy,
|
||||||
protected val referenceTime: Date = Date(),
|
protected val referenceTime: Date = Date(),
|
||||||
protected val keyGenerationPolicy: AlgorithmSuite = policy.keyGenerationAlgorithmSuite
|
protected val preferences: AlgorithmSuite = policy.keyGenerationAlgorithmSuite
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build an OpenPGP v4 key. The result will be a key compliant to RFC4880, RFC6637.
|
||||||
|
*
|
||||||
|
* @param keyType type of the primary key
|
||||||
|
* @param flags key flags for the primary key. Defaults to [KeyFlag.CERTIFY_OTHER].
|
||||||
|
* @return [V4OpenPgpKeyBuilder] which can be further modified, e.g. add subkeys, user-ids etc.
|
||||||
|
*/
|
||||||
fun buildV4Key(
|
fun buildV4Key(
|
||||||
keyType: KeyType,
|
keyType: KeyType,
|
||||||
): V4OpenPgpKeyBuilder =
|
flags: List<KeyFlag>? = listOf(KeyFlag.CERTIFY_OTHER)
|
||||||
V4OpenPgpKeyBuilder(keyType, policy, referenceTime, keyGenerationPolicy)
|
): V4OpenPgpKeyBuilder = V4OpenPgpKeyBuilder(keyType, flags, policy, referenceTime, preferences)
|
||||||
|
|
||||||
class V4OpenPgpKeyBuilder(
|
/**
|
||||||
keyType: KeyType,
|
* Builder for version 4 OpenPGP keys.
|
||||||
|
*
|
||||||
|
* @param primaryKeyType type of the primary key
|
||||||
|
* @param primaryFlags list of key-flags for the primary key. Can be `null`.
|
||||||
|
* @param policy algorithm policy
|
||||||
|
* @param referenceTime reference time for key generation
|
||||||
|
* @param preferences set of algorithm preferences and enabled features for the key
|
||||||
|
*/
|
||||||
|
class V4OpenPgpKeyBuilder
|
||||||
|
internal constructor(
|
||||||
|
primaryKeyType: KeyType,
|
||||||
|
primaryFlags: List<KeyFlag>?,
|
||||||
policy: Policy,
|
policy: Policy,
|
||||||
referenceTime: Date,
|
referenceTime: Date,
|
||||||
keyGenerationPolicy: AlgorithmSuite
|
preferences: AlgorithmSuite
|
||||||
) : OpenPgpKeyBuilder(policy, referenceTime, keyGenerationPolicy) {
|
) : OpenPgpKeyBuilder(policy, referenceTime, preferences) {
|
||||||
|
|
||||||
private val primaryKey =
|
private val primaryKey =
|
||||||
BaseOpenPgpKeyBuilder.BaseV4PrimaryKeyBuilder(keyType, referenceTime, policy)
|
BaseOpenPgpKeyBuilder.BaseV4PrimaryKeyBuilder(primaryKeyType, referenceTime, policy)
|
||||||
|
|
||||||
private val subkeys = mutableListOf<BaseOpenPgpKeyBuilder.BaseV4SubkeyBuilder>()
|
private val subkeys = mutableListOf<BaseOpenPgpKeyBuilder.BaseV4SubkeyBuilder>()
|
||||||
|
|
||||||
fun addUserId(
|
private val preferencesCallback =
|
||||||
userId: CharSequence,
|
SelfSignatureSubpackets.applyHashed {
|
||||||
algorithmSuite: AlgorithmSuite = keyGenerationPolicy,
|
setPreferredSymmetricKeyAlgorithms(preferences.symmetricKeyAlgorithms)
|
||||||
subpacketsCallback: SelfSignatureSubpackets.Callback = SelfSignatureSubpackets.nop()
|
setPreferredHashAlgorithms(preferences.hashAlgorithms)
|
||||||
) = apply {
|
setPreferredCompressionAlgorithms(preferences.compressionAlgorithms)
|
||||||
primaryKey.userId(userId, algorithmSuite, subpacketsCallback = subpacketsCallback)
|
setFeatures(*preferences.features.toTypedArray())
|
||||||
|
primaryFlags?.let { setKeyFlags(*it.toTypedArray()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a user-id to the key. The subpackets of the binding signature are prepopulated,
|
||||||
|
* setting algorithm preferences and features. However, the subpackets can still be modified
|
||||||
|
* using the provided [subpacketsCallback].
|
||||||
|
*
|
||||||
|
* @param userId user-id to add
|
||||||
|
* @param subpacketsCallback callback to modify the user-id binding signatures subpackets.
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
|
fun addUserId(
|
||||||
|
userId: CharSequence,
|
||||||
|
subpacketsCallback: SelfSignatureSubpackets.Callback = SelfSignatureSubpackets.nop()
|
||||||
|
) = apply {
|
||||||
|
primaryKey.userId(
|
||||||
|
userId, subpacketsCallback = preferencesCallback.then(subpacketsCallback))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a user-attribute to the key. The subpackets of the binding signature are
|
||||||
|
* prepopulated, setting algorithm preferences and features. However, the subpackets can
|
||||||
|
* still be modified using the provided [subpacketsCallback].
|
||||||
|
*
|
||||||
|
* @param attribute user-attribute to add
|
||||||
|
* @param subpacketsCallback callback to modify the user-attribute binding signatures
|
||||||
|
* subpackets.
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
fun addUserAttribute(
|
fun addUserAttribute(
|
||||||
attribute: PGPUserAttributeSubpacketVector,
|
attribute: PGPUserAttributeSubpacketVector,
|
||||||
algorithmSuite: AlgorithmSuite = keyGenerationPolicy,
|
|
||||||
subpacketsCallback: SelfSignatureSubpackets.Callback = SelfSignatureSubpackets.nop()
|
subpacketsCallback: SelfSignatureSubpackets.Callback = SelfSignatureSubpackets.nop()
|
||||||
) = apply {
|
) = apply {
|
||||||
primaryKey.userAttribute(
|
primaryKey.userAttribute(
|
||||||
attribute, algorithmSuite, subpacketsCallback = subpacketsCallback)
|
attribute, subpacketsCallback = preferencesCallback.then(subpacketsCallback))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a subkey to the key. The subpackets of the binding signature will be populated with
|
||||||
|
* issuer information, the passed in [bindingTime] as signature creation time and given
|
||||||
|
* key-flags (if non-null). You can further manipulate the subpackets by passing in an
|
||||||
|
* appropriate [subpacketsCallback].
|
||||||
|
*
|
||||||
|
* @param keyType type of the key
|
||||||
|
* @param creationTime creation time of the key. Defaults to [referenceTime]
|
||||||
|
* @param bindingTime creation time of the binding signature. Defaults to [creationTime]
|
||||||
|
* @param keyFlags list of key-flags for the subkey.
|
||||||
|
* @param subpacketsCallback callback to modify the subpackets of the binding signature
|
||||||
|
*/
|
||||||
fun addSubkey(
|
fun addSubkey(
|
||||||
keyType: KeyType,
|
keyType: KeyType,
|
||||||
creationTime: Date = referenceTime,
|
creationTime: Date = referenceTime,
|
||||||
bindingTime: Date = creationTime,
|
bindingTime: Date = creationTime,
|
||||||
keyFlags: List<KeyFlag>?
|
keyFlags: List<KeyFlag>? = null,
|
||||||
|
subpacketsCallback: SelfSignatureSubpackets.Callback = SelfSignatureSubpackets.nop()
|
||||||
) =
|
) =
|
||||||
addSubkey(
|
addSubkey(
|
||||||
BaseOpenPgpKeyBuilder.BaseV4SubkeyBuilder(
|
BaseOpenPgpKeyBuilder.BaseV4SubkeyBuilder(
|
||||||
keyType, creationTime, policy, primaryKey),
|
keyType, creationTime, policy, primaryKey),
|
||||||
bindingTime,
|
SelfSignatureSubpackets.applyHashed {
|
||||||
keyFlags)
|
setSignatureCreationTime(bindingTime)
|
||||||
|
keyFlags?.let { setKeyFlags(it) }
|
||||||
|
}
|
||||||
|
.then(subpacketsCallback))
|
||||||
|
|
||||||
fun addSubkey(
|
fun addSubkey(
|
||||||
subkeyBuilder: BaseOpenPgpKeyBuilder.BaseV4SubkeyBuilder,
|
subkeyBuilder: BaseOpenPgpKeyBuilder.BaseV4SubkeyBuilder,
|
||||||
bindingTime: Date = subkeyBuilder.creationTime,
|
subpacketsCallback: SelfSignatureSubpackets.Callback = SelfSignatureSubpackets.nop()
|
||||||
keyFlags: List<KeyFlag>?
|
|
||||||
) = apply {
|
) = apply {
|
||||||
subkeys.add(
|
subkeys.add(subkeyBuilder.bindingSignature(subpacketsCallback = subpacketsCallback))
|
||||||
subkeyBuilder.also {
|
|
||||||
it.bindingSignature(
|
|
||||||
bindingTime,
|
|
||||||
subpacketsCallback =
|
|
||||||
object : SelfSignatureSubpackets.Callback {
|
|
||||||
override fun modifyHashedSubpackets(
|
|
||||||
hashedSubpackets: SelfSignatureSubpackets
|
|
||||||
) {
|
|
||||||
hashedSubpackets.setSignatureCreationTime(bindingTime)
|
|
||||||
keyFlags?.let { flagList ->
|
|
||||||
hashedSubpackets.setKeyFlags(flagList)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addEncryptionSubkey(
|
fun addEncryptionSubkey(
|
||||||
|
@ -113,44 +164,18 @@ open class OpenPgpKeyBuilder(
|
||||||
protector: SecretKeyRingProtector = SecretKeyRingProtector.unprotectedKeys()
|
protector: SecretKeyRingProtector = SecretKeyRingProtector.unprotectedKeys()
|
||||||
): PGPSecretKeyRing {
|
): PGPSecretKeyRing {
|
||||||
|
|
||||||
// Add DK sig in case of no user-id
|
// add a direct key sig with preferences
|
||||||
if (primaryKey.isWithoutUserIds()) {
|
primaryKey.directKeySignature(subpacketsCallback = preferencesCallback)
|
||||||
primaryKey.directKeySignature(
|
|
||||||
subpacketsCallback = defaultPrimarySubpacketsCallback())
|
|
||||||
}
|
|
||||||
|
|
||||||
return PGPSecretKeyRing(
|
return PGPSecretKeyRing(
|
||||||
mutableListOf(
|
mutableListOf(
|
||||||
PGPSecretKey(
|
toSecretKey(primaryKey, true, protector.getEncryptor(primaryKey.key.keyID)))
|
||||||
primaryKey.key.privateKey,
|
|
||||||
primaryKey.key.publicKey,
|
|
||||||
ImplementationFactory.getInstance().v4FingerprintCalculator,
|
|
||||||
true,
|
|
||||||
protector.getEncryptor(primaryKey.key.keyID)))
|
|
||||||
.plus(
|
.plus(
|
||||||
subkeys.map {
|
subkeys.map {
|
||||||
PGPSecretKey(
|
toSecretKey(it, false, protector.getEncryptor(it.key.keyID))
|
||||||
it.key.privateKey,
|
|
||||||
it.key.publicKey,
|
|
||||||
ImplementationFactory.getInstance().v4FingerprintCalculator,
|
|
||||||
false,
|
|
||||||
protector.getEncryptor(it.key.keyID))
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun defaultPrimarySubpacketsCallback(): SelfSignatureSubpackets.Callback =
|
|
||||||
object : SelfSignatureSubpackets.Callback {
|
|
||||||
override fun modifyHashedSubpackets(hashedSubpackets: SelfSignatureSubpackets) {
|
|
||||||
hashedSubpackets.apply {
|
|
||||||
setPreferredHashAlgorithms(keyGenerationPolicy.hashAlgorithms)
|
|
||||||
setPreferredSymmetricKeyAlgorithms(
|
|
||||||
keyGenerationPolicy.symmetricKeyAlgorithms)
|
|
||||||
setPreferredCompressionAlgorithms(keyGenerationPolicy.compressionAlgorithms)
|
|
||||||
setKeyFlags(KeyFlag.CERTIFY_OTHER)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun toSecretKey(
|
private fun toSecretKey(
|
||||||
key: BaseOpenPgpKeyBuilder.BaseV4KeyBuilder<*>,
|
key: BaseOpenPgpKeyBuilder.BaseV4KeyBuilder<*>,
|
||||||
isPrimaryKey: Boolean,
|
isPrimaryKey: Boolean,
|
||||||
|
|
|
@ -4,6 +4,7 @@ import org.bouncycastle.bcpg.attr.ImageAttribute
|
||||||
import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVectorGenerator
|
import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVectorGenerator
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.pgpainless.PGPainless
|
import org.pgpainless.PGPainless
|
||||||
|
import org.pgpainless.algorithm.KeyFlag
|
||||||
import org.pgpainless.key.generation.type.KeyType
|
import org.pgpainless.key.generation.type.KeyType
|
||||||
import org.pgpainless.key.generation.type.eddsa.EdDSACurve
|
import org.pgpainless.key.generation.type.eddsa.EdDSACurve
|
||||||
import org.pgpainless.key.generation.type.xdh.XDHSpec
|
import org.pgpainless.key.generation.type.xdh.XDHSpec
|
||||||
|
@ -18,7 +19,7 @@ class OpenPgpKeyBuilderTest {
|
||||||
val date = DateUtil.parseUTCDate("2020-04-01 10:00:00 UTC")
|
val date = DateUtil.parseUTCDate("2020-04-01 10:00:00 UTC")
|
||||||
val key =
|
val key =
|
||||||
OpenPgpKeyBuilder(Policy.getInstance(), date)
|
OpenPgpKeyBuilder(Policy.getInstance(), date)
|
||||||
.buildV4Key(KeyType.EDDSA(EdDSACurve._Ed25519))
|
.buildV4Key(KeyType.EDDSA(EdDSACurve._Ed25519), listOf(KeyFlag.CERTIFY_OTHER))
|
||||||
.addUserId("Alice")
|
.addUserId("Alice")
|
||||||
.addUserAttribute(
|
.addUserAttribute(
|
||||||
PGPUserAttributeSubpacketVectorGenerator()
|
PGPUserAttributeSubpacketVectorGenerator()
|
||||||
|
@ -34,7 +35,7 @@ class OpenPgpKeyBuilderTest {
|
||||||
fun minimal() {
|
fun minimal() {
|
||||||
val key =
|
val key =
|
||||||
OpenPgpKeyBuilder(Policy.getInstance())
|
OpenPgpKeyBuilder(Policy.getInstance())
|
||||||
.buildV4Key(KeyType.EDDSA(EdDSACurve._Ed25519))
|
.buildV4Key(KeyType.EDDSA(EdDSACurve._Ed25519), listOf(KeyFlag.CERTIFY_OTHER))
|
||||||
.build()
|
.build()
|
||||||
println(PGPainless.asciiArmor(key))
|
println(PGPainless.asciiArmor(key))
|
||||||
}
|
}
|
||||||
|
@ -43,7 +44,7 @@ class OpenPgpKeyBuilderTest {
|
||||||
fun minimalWithUserId() {
|
fun minimalWithUserId() {
|
||||||
val key =
|
val key =
|
||||||
OpenPgpKeyBuilder(Policy.getInstance())
|
OpenPgpKeyBuilder(Policy.getInstance())
|
||||||
.buildV4Key(KeyType.EDDSA(EdDSACurve._Ed25519))
|
.buildV4Key(KeyType.EDDSA(EdDSACurve._Ed25519), listOf(KeyFlag.CERTIFY_OTHER))
|
||||||
.addUserId("Alice <alice@pgpainless.org>")
|
.addUserId("Alice <alice@pgpainless.org>")
|
||||||
.build()
|
.build()
|
||||||
println(PGPainless.asciiArmor(key))
|
println(PGPainless.asciiArmor(key))
|
||||||
|
|
Loading…
Reference in a new issue