pgpainless/pgpainless-core/src/main/kotlin/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditorInterfac...

679 lines
27 KiB
Kotlin

// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.key.modification.secretkeyring
import java.io.IOException
import java.security.InvalidAlgorithmParameterException
import java.security.NoSuchAlgorithmException
import java.util.*
import org.bouncycastle.openpgp.*
import org.pgpainless.algorithm.KeyFlag
import org.pgpainless.key.OpenPgpFingerprint
import org.pgpainless.key.generation.KeySpec
import org.pgpainless.key.protection.KeyRingProtectionSettings
import org.pgpainless.key.protection.SecretKeyRingProtector
import org.pgpainless.key.util.RevocationAttributes
import org.pgpainless.signature.subpackets.RevocationSignatureSubpackets
import org.pgpainless.signature.subpackets.SelfSignatureSubpackets
import org.pgpainless.util.Passphrase
import org.pgpainless.util.selection.userid.SelectUserId
interface SecretKeyRingEditorInterface {
/**
* Editors reference time. This time is used as creation date for new signatures, or as
* reference when evaluating expiration of existing signatures.
*/
val referenceTime: Date
/**
* Add a user-id to the key ring.
*
* @param userId user-id
* @param protector protector to unlock the secret key
* @return the builder
* @throws PGPException in case we cannot generate a signature for the user-id
*/
@Throws(PGPException::class)
fun addUserId(userId: CharSequence, protector: SecretKeyRingProtector) =
addUserId(userId, null, protector)
/**
* Add a user-id to the key ring.
*
* @param userId user-id
* @param callback callback to modify the self-signature subpackets
* @param protector protector to unlock the secret key
* @return the builder
* @throws PGPException in case we cannot generate a signature for the user-id
*/
@Throws(PGPException::class)
fun addUserId(
userId: CharSequence,
callback: SelfSignatureSubpackets.Callback? = null,
protector: SecretKeyRingProtector
): SecretKeyRingEditorInterface
/**
* Add a user-id to the key ring and mark it as primary. If the user-id is already present, a
* new certification signature will be created.
*
* @param userId user id
* @param protector protector to unlock the secret key
* @return the builder
* @throws PGPException in case we cannot generate a signature for the user-id
*/
@Throws(PGPException::class)
fun addPrimaryUserId(
userId: CharSequence,
protector: SecretKeyRingProtector
): SecretKeyRingEditorInterface
/**
* Convenience method to revoke selected user-ids using soft revocation signatures. The
* revocation will use [RevocationAttributes.Reason.USER_ID_NO_LONGER_VALID], so that the
* user-id can be re-certified at a later point.
*
* @param selector selector to select user-ids
* @param protector protector to unlock the primary key
* @return the builder
* @throws PGPException in case we cannot generate a revocation signature for the user-id
*/
@Deprecated(
"Use of SelectUserId class is deprecated.",
ReplaceWith("removeUserId(protector, predicate)"))
@Throws(PGPException::class)
fun removeUserId(selector: SelectUserId, protector: SecretKeyRingProtector) =
removeUserId(protector, selector)
/**
* Convenience method to revoke selected user-ids using soft revocation signatures. The
* revocation will use [RevocationAttributes.Reason.USER_ID_NO_LONGER_VALID], so that the
* user-id can be re-certified at a later point.
*
* @param protector protector to unlock the primary key
* @param predicate predicate to select user-ids for revocation
* @return the builder
* @throws PGPException in case we cannot generate a revocation signature for the user-id
*/
@Throws(PGPException::class)
fun removeUserId(
protector: SecretKeyRingProtector,
predicate: (String) -> Boolean
): SecretKeyRingEditorInterface
/**
* Convenience method to revoke a single user-id using a soft revocation signature. The
* revocation will use [RevocationAttributes.Reason.USER_ID_NO_LONGER_VALID] so that the user-id
* can be re-certified at a later point.
*
* @param userId user-id to revoke
* @param protector protector to unlock the primary key
* @return the builder
* @throws PGPException in case we cannot generate a revocation signature for the user-id
*/
@Throws(PGPException::class)
fun removeUserId(
userId: CharSequence,
protector: SecretKeyRingProtector
): SecretKeyRingEditorInterface
/**
* Replace a user-id on the key with a new one. The old user-id gets soft revoked and the new
* user-id gets bound with the same signature subpackets as the old one, with one exception: If
* the old user-id was implicitly primary (did not carry a
* [org.bouncycastle.bcpg.sig.PrimaryUserID] packet, but effectively was primary), then the new
* user-id will be explicitly marked as primary.
*
* @param oldUserId old user-id
* @param newUserId new user-id
* @param protector protector to unlock the secret key
* @return the builder
* @throws PGPException in case we cannot generate a revocation and certification signature
* @throws java.util.NoSuchElementException if the old user-id was not found on the key; or if
* the oldUserId was already invalid
*/
@Throws(PGPException::class)
fun replaceUserId(
oldUserId: CharSequence,
newUserId: CharSequence,
protector: SecretKeyRingProtector
): SecretKeyRingEditorInterface
/**
* Add a subkey to the key ring. The subkey will be generated from the provided [KeySpec].
*
* @param keySpec key specification
* @param subkeyPassphrase passphrase to encrypt the sub key
* @param callback callback to modify the subpackets of the subkey binding signature
* @param protector protector to unlock the secret key of the key ring
* @return the builder
* @throws InvalidAlgorithmParameterException in case the user wants to use invalid parameters
* for the key
* @throws NoSuchAlgorithmException in case of missing algorithm support in the crypto backend
* @throws PGPException in case we cannot generate a binding signature for the subkey
* @throws IOException in case of an IO error
*/
@Throws(
PGPException::class,
IOException::class,
InvalidAlgorithmParameterException::class,
NoSuchAlgorithmException::class)
fun addSubKey(
keySpec: KeySpec,
subkeyPassphrase: Passphrase,
callback: SelfSignatureSubpackets.Callback? = null,
protector: SecretKeyRingProtector
): SecretKeyRingEditorInterface
/**
* Add a subkey to the key ring.
*
* @param subkey subkey key pair
* @param callback callback to modify the subpackets of the subkey binding signature
* @param subkeyProtector protector to unlock and encrypt the subkey
* @param primaryKeyProtector protector to unlock the primary key
* @param keyFlag first mandatory key flag for the subkey
* @param keyFlags optional additional key flags
* @return builder
* @throws PGPException in case we cannot generate a binding signature for the subkey
* @throws IOException in case of an IO error
*/
@Throws(PGPException::class, IOException::class)
fun addSubKey(
subkey: PGPKeyPair,
callback: SelfSignatureSubpackets.Callback?,
subkeyProtector: SecretKeyRingProtector,
primaryKeyProtector: SecretKeyRingProtector,
keyFlag: KeyFlag,
vararg keyFlags: KeyFlag
): SecretKeyRingEditorInterface
/**
* Revoke the key ring using a hard revocation.
*
* @param protector protector of the primary key
* @return the builder
* @throws PGPException in case we cannot generate a revocation signature
*/
@Throws(PGPException::class)
fun revoke(protector: SecretKeyRingProtector) = revoke(protector, null as RevocationAttributes?)
/**
* Revoke the key ring using the provided revocation attributes. The attributes define, whether
* the revocation was a hard revocation or not.
*
* @param protector protector of the primary key
* @param revocationAttributes reason for the revocation
* @return the builder
* @throws PGPException in case we cannot generate a revocation signature
*/
@Throws(PGPException::class)
fun revoke(
protector: SecretKeyRingProtector,
revocationAttributes: RevocationAttributes? = null
): SecretKeyRingEditorInterface
/**
* Revoke the key ring. You can use the [RevocationSignatureSubpackets.Callback] to modify the
* revocation signatures subpackets, e.g. in order to define whether this is a hard or soft
* revocation.
*
* @param protector protector to unlock the primary secret key
* @param callback callback to modify the revocations subpackets
* @return builder
* @throws PGPException in case we cannot generate a revocation signature
*/
@Throws(PGPException::class)
fun revoke(
protector: SecretKeyRingProtector,
callback: RevocationSignatureSubpackets.Callback?
): SecretKeyRingEditorInterface
/**
* Revoke the subkey binding signature of a subkey. The subkey with the provided fingerprint
* will be revoked. If no suitable subkey is found, a [NoSuchElementException] will be thrown.
*
* @param fingerprint fingerprint of the subkey to be revoked
* @param protector protector to unlock the primary key
* @return the builder
* @throws PGPException in case we cannot generate a revocation signature for the subkey
*/
@Throws(PGPException::class)
fun revokeSubKey(fingerprint: OpenPgpFingerprint, protector: SecretKeyRingProtector) =
revokeSubKey(fingerprint, protector, null)
/**
* Revoke the subkey binding signature of a subkey. The subkey with the provided fingerprint
* will be revoked. If no suitable subkey is found, a [NoSuchElementException] will be thrown.
*
* @param fingerprint fingerprint of the subkey to be revoked
* @param protector protector to unlock the primary key
* @param revocationAttributes reason for the revocation
* @return the builder
* @throws PGPException in case we cannot generate a revocation signature for the subkey
*/
@Throws(PGPException::class)
fun revokeSubKey(
fingerprint: OpenPgpFingerprint,
protector: SecretKeyRingProtector,
revocationAttributes: RevocationAttributes? = null
): SecretKeyRingEditorInterface =
revokeSubKey(fingerprint.keyId, protector, revocationAttributes)
/**
* Revoke the subkey binding signature of a subkey. The subkey with the provided key-id will be
* revoked. If no suitable subkey is found, a [NoSuchElementException] will be thrown.
*
* @param subkeyId id of the subkey
* @param protector protector to unlock the primary key
* @return the builder
* @throws PGPException in case we cannot generate a revocation signature for the subkey
*/
@Throws(PGPException::class)
fun revokeSubKey(subkeyId: Long, protector: SecretKeyRingProtector) =
revokeSubKey(subkeyId, protector, null as RevocationAttributes?)
/**
* Revoke the subkey binding signature of a subkey. The subkey with the provided key-id will be
* revoked. If no suitable subkey is found, a [NoSuchElementException] will be thrown.
*
* @param subkeyId id of the subkey
* @param protector protector to unlock the primary key
* @param revocationAttributes reason for the revocation
* @return the builder
* @throws PGPException in case we cannot generate a revocation signature for the subkey
*/
@Throws(PGPException::class)
fun revokeSubKey(
subkeyId: Long,
protector: SecretKeyRingProtector,
revocationAttributes: RevocationAttributes? = null
): SecretKeyRingEditorInterface
/**
* Revoke the subkey binding signature of a subkey. The subkey with the provided key-id will be
* revoked. If no suitable subkey is found, a [NoSuchElementException] will be thrown.
*
* The provided subpackets callback is used to modify the revocation signatures subpackets.
*
* @param subkeyId id of the subkey
* @param protector protector to unlock the secret key ring
* @param callback callback which can be used to modify the subpackets of the revocation
* signature
* @return the builder
* @throws PGPException in case we cannot generate a revocation signature for the subkey
*/
@Throws(PGPException::class)
fun revokeSubKey(
subkeyId: Long,
protector: SecretKeyRingProtector,
callback: RevocationSignatureSubpackets.Callback?
): SecretKeyRingEditorInterface
/**
* Hard-revoke the given userID.
*
* @param userId userId to revoke
* @param protector protector to unlock the primary key
* @return the builder
* @throws PGPException in case we cannot generate a revocation signature for the user-id
*/
@Throws(PGPException::class)
fun revokeUserId(userId: CharSequence, protector: SecretKeyRingProtector) =
revokeUserId(userId, protector, null as RevocationAttributes?)
/**
* Revoke the given userID using the provided revocation attributes.
*
* @param userId userId to revoke
* @param protector protector to unlock the primary key
* @param revocationAttributes reason for the revocation
* @return the builder
* @throws PGPException in case we cannot generate a revocation signature for the user-id
*/
@Throws(PGPException::class)
fun revokeUserId(
userId: CharSequence,
protector: SecretKeyRingProtector,
revocationAttributes: RevocationAttributes? = null
): SecretKeyRingEditorInterface
/**
* Revoke the provided user-id. Note: If you don't provide a
* [RevocationSignatureSubpackets.Callback] which sets a revocation reason
* ([RevocationAttributes]), the revocation will be considered hard. So if you intend to
* re-certify the user-id at a later point to make it valid again, make sure to set a soft
* revocation reason in the signatures hashed area using the subpacket callback.
*
* @param userId userid to be revoked
* @param protector protector to unlock the primary secret key
* @param callback callback to modify the revocations subpackets
* @return builder
* @throws PGPException in case we cannot generate a revocation signature for the user-id
*/
@Throws(PGPException::class)
fun revokeUserId(
userId: CharSequence,
protector: SecretKeyRingProtector,
callback: RevocationSignatureSubpackets.Callback?
): SecretKeyRingEditorInterface
/**
* Revoke all user-ids that match the provided [SelectUserId] filter. The provided
* [RevocationAttributes] will be set as reason for revocation in each revocation signature.
*
* Note: If you intend to re-certify these user-ids at a later point, make sure to choose a soft
* revocation reason. See [RevocationAttributes.Reason] for more information.
*
* @param selector user-id selector
* @param protector protector to unlock the primary secret key
* @param revocationAttributes revocation attributes
* @return builder
* @throws PGPException in case we cannot generate a revocation signature for the user-id
*/
@Throws(PGPException::class)
@Deprecated(
"Use of SelectUserId class is deprecated.",
ReplaceWith("revokeUserIds(protector, revocationAttributes, predicate)"))
fun revokeUserIds(
selector: SelectUserId,
protector: SecretKeyRingProtector,
revocationAttributes: RevocationAttributes?
) = revokeUserIds(protector, revocationAttributes, selector)
/**
* Revoke all user-ids that match the provided [SelectUserId] filter. The provided
* [RevocationAttributes] will be set as reason for revocation in each revocation signature.
*
* Note: If you intend to re-certify these user-ids at a later point, make sure to choose a soft
* revocation reason. See [RevocationAttributes.Reason] for more information.
*
* @param protector protector to unlock the primary secret key
* @param revocationAttributes revocation attributes
* @param predicate to select user-ids for revocation
* @return builder
* @throws PGPException in case we cannot generate a revocation signature for the user-id
*/
@Throws(PGPException::class)
fun revokeUserIds(
protector: SecretKeyRingProtector,
revocationAttributes: RevocationAttributes?,
predicate: (String) -> Boolean
): SecretKeyRingEditorInterface
/**
* Revoke all user-ids that match the provided [SelectUserId] filter. The provided
* [RevocationSignatureSubpackets.Callback] will be used to modify the revocation signatures
* subpackets.
*
* Note: If you intend to re-certify these user-ids at a later point, make sure to set a soft
* revocation reason in the revocation signatures hashed subpacket area using the callback.
*
* See [RevocationAttributes.Reason] for more information.
*
* @param selector user-id selector
* @param protector protector to unlock the primary secret key
* @param callback callback to modify the revocations subpackets
* @return builder
* @throws PGPException in case we cannot generate a revocation signature for the user-id
*/
@Throws(PGPException::class)
@Deprecated(
"Use of SelectUserId class is deprecated.",
ReplaceWith("revokeUserIds(protector, callback, predicate)"))
fun revokeUserIds(
selector: SelectUserId,
protector: SecretKeyRingProtector,
callback: RevocationSignatureSubpackets.Callback?
) = revokeUserIds(protector, callback, selector)
/**
* Revoke all user-ids that match the provided [SelectUserId] filter. The provided
* [RevocationSignatureSubpackets.Callback] will be used to modify the revocation signatures
* subpackets.
*
* Note: If you intend to re-certify these user-ids at a later point, make sure to set a soft
* revocation reason in the revocation signatures hashed subpacket area using the callback.
*
* See [RevocationAttributes.Reason] for more information.
*
* @param protector protector to unlock the primary secret key
* @param callback callback to modify the revocations subpackets
* @param predicate to select user-ids for revocation
* @return builder
* @throws PGPException in case we cannot generate a revocation signature for the user-id
*/
@Throws(PGPException::class)
fun revokeUserIds(
protector: SecretKeyRingProtector,
callback: RevocationSignatureSubpackets.Callback?,
predicate: (String) -> Boolean
): SecretKeyRingEditorInterface
/**
* Set the expiration date for the primary key of the key ring. If the key is supposed to never
* expire, then an expiration date of null is expected.
*
* @param expiration new expiration date or null
* @param protector to unlock the secret key
* @return the builder
* @throws PGPException in case we cannot generate a new self-signature with the changed
* expiration date
*/
@Throws(PGPException::class)
fun setExpirationDate(
expiration: Date?,
protector: SecretKeyRingProtector
): SecretKeyRingEditorInterface
/**
* Set the expiration date for the subkey identified by the given keyId to the given expiration
* date. If the key is supposed to never expire, then an expiration date of null is expected.
*
* @param expiration new expiration date of null
* @param keyId id of the subkey
* @param protector to unlock the secret key
* @return the builder
* @throws PGPException in case we cannot generate a new subkey-binding or self-signature with
* the changed expiration date
*/
@Throws(PGPException::class)
fun setExpirationDateOfSubkey(
expiration: Date?,
keyId: Long,
protector: SecretKeyRingProtector
): SecretKeyRingEditorInterface
/**
* Create a minimal, self-authorizing revocation certificate, containing only the primary key
* and a revocation signature. This type of revocation certificates was introduced in OpenPGP
* v6. This method has no side effects on the original key and will leave it intact.
*
* @param protector protector to unlock the primary key.
* @param revocationAttributes reason for the revocation (key revocation)
* @return minimal revocation certificate
* @throws PGPException in case we cannot generate a revocation signature
*/
@Throws(PGPException::class)
fun createMinimalRevocationCertificate(
protector: SecretKeyRingProtector,
revocationAttributes: RevocationAttributes?
): PGPPublicKeyRing
/**
* Create a detached revocation certificate, which can be used to revoke the whole key. The
* original key will not be modified by this method.
*
* @param protector protector to unlock the primary key.
* @param revocationAttributes reason for the revocation
* @return revocation certificate
* @throws PGPException in case we cannot generate a revocation certificate
*/
@Throws(PGPException::class)
fun createRevocation(
protector: SecretKeyRingProtector,
revocationAttributes: RevocationAttributes?
): PGPSignature
/**
* Create a detached revocation certificate, which can be used to revoke the specified subkey.
* The original key will not be modified by this method.
*
* @param subkeyId id of the subkey to be revoked
* @param protector protector to unlock the primary key.
* @param revocationAttributes reason for the revocation
* @return revocation certificate
* @throws PGPException in case we cannot generate a revocation certificate
*/
@Throws(PGPException::class)
fun createRevocation(
subkeyId: Long,
protector: SecretKeyRingProtector,
revocationAttributes: RevocationAttributes?
): PGPSignature
/**
* Create a detached revocation certificate, which can be used to revoke the specified subkey.
* The original key will not be modified by this method.
*
* @param subkeyId id of the subkey to be revoked
* @param protector protector to unlock the primary key.
* @param callback callback to modify the subpackets of the revocation certificate.
* @return revocation certificate
* @throws PGPException in case we cannot generate a revocation certificate
*/
@Throws(PGPException::class)
fun createRevocation(
subkeyId: Long,
protector: SecretKeyRingProtector,
callback: RevocationSignatureSubpackets.Callback?
): PGPSignature
/**
* Create a detached revocation certificate, which can be used to revoke the specified subkey.
* The original key will not be modified by this method.
*
* @param subkeyFingerprint fingerprint of the subkey to be revoked
* @param protector protector to unlock the primary key.
* @param revocationAttributes reason for the revocation
* @return revocation certificate
* @throws PGPException in case we cannot generate a revocation certificate
*/
@Throws(PGPException::class)
fun createRevocation(
subkeyFingerprint: OpenPgpFingerprint,
protector: SecretKeyRingProtector,
revocationAttributes: RevocationAttributes?
): PGPSignature
/**
* Change the passphrase of the whole key ring.
*
* @param oldPassphrase old passphrase (empty, if the key was unprotected)
* @return next builder step
*/
fun changePassphraseFromOldPassphrase(oldPassphrase: Passphrase) =
changePassphraseFromOldPassphrase(
oldPassphrase, KeyRingProtectionSettings.secureDefaultSettings())
/**
* Change the passphrase of the whole key ring.
*
* @param oldPassphrase old passphrase (empty, if the key was unprotected)
* @param oldProtectionSettings custom settings for the old passphrase
* @return next builder step
*/
fun changePassphraseFromOldPassphrase(
oldPassphrase: Passphrase,
oldProtectionSettings: KeyRingProtectionSettings =
KeyRingProtectionSettings.secureDefaultSettings()
): WithKeyRingEncryptionSettings
/**
* Change the passphrase of a single subkey in the key ring.
*
* Note: While it is a valid use-case to have different passphrases per subKey, this is one of
* the reasons why OpenPGP sucks in practice.
*
* @param keyId id of the subkey
* @param oldPassphrase old passphrase (empty if the key was unprotected)
* @return next builder step
*/
fun changeSubKeyPassphraseFromOldPassphrase(keyId: Long, oldPassphrase: Passphrase) =
changeSubKeyPassphraseFromOldPassphrase(
keyId, oldPassphrase, KeyRingProtectionSettings.secureDefaultSettings())
/**
* Change the passphrase of a single subkey in the key ring.
*
* Note: While it is a valid use-case to have different passphrases per subKey, this is one of
* the reasons why OpenPGP sucks in practice.
*
* @param keyId id of the subkey
* @param oldPassphrase old passphrase (empty if the key was unprotected)
* @param oldProtectionSettings custom settings for the old passphrase
* @return next builder step
*/
fun changeSubKeyPassphraseFromOldPassphrase(
keyId: Long,
oldPassphrase: Passphrase,
oldProtectionSettings: KeyRingProtectionSettings
): WithKeyRingEncryptionSettings
interface WithKeyRingEncryptionSettings {
/**
* Set secure default settings for the symmetric passphrase encryption. Note that this
* obviously has no effect if you decide to set [WithPassphrase.toNoPassphrase].
*
* @return next builder step
*/
fun withSecureDefaultSettings(): WithPassphrase
/**
* Set custom settings for the symmetric passphrase encryption.
*
* @param settings custom settings
* @return next builder step
*/
fun withCustomSettings(settings: KeyRingProtectionSettings): WithPassphrase
}
interface WithPassphrase {
/**
* Set the passphrase.
*
* @param passphrase passphrase
* @return editor builder
* @throws PGPException in case the passphrase cannot be changed
*/
@Throws(PGPException::class)
fun toNewPassphrase(passphrase: Passphrase): SecretKeyRingEditorInterface
/**
* Leave the key unprotected.
*
* @return editor builder
* @throws PGPException in case the passphrase cannot be changed
*/
@Throws(PGPException::class) fun toNoPassphrase(): SecretKeyRingEditorInterface
}
/**
* Return the [PGPSecretKeyRing].
*
* @return the key
*/
fun done(): PGPSecretKeyRing
fun addSubKey(
keySpec: KeySpec,
subkeyPassphrase: Passphrase,
protector: SecretKeyRingProtector
): SecretKeyRingEditorInterface
}