From 0271bae9b434865bcb6c1974b5a054fcf2ecb3a5 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 14 Sep 2023 16:31:52 +0200 Subject: [PATCH] Kotlin conversion: RevocationAttributes --- .../key/util/RevocationAttributes.java | 253 ------------------ .../key/util/RevocationAttributes.kt | 155 +++++++++++ 2 files changed, 155 insertions(+), 253 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/util/RevocationAttributes.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/key/util/RevocationAttributes.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/util/RevocationAttributes.java b/pgpainless-core/src/main/java/org/pgpainless/key/util/RevocationAttributes.java deleted file mode 100644 index f84af5d3..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/util/RevocationAttributes.java +++ /dev/null @@ -1,253 +0,0 @@ -// SPDX-FileCopyrightText: 2020 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key.util; - -import javax.annotation.Nonnull; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -public final class RevocationAttributes { - - /** - * Reason for revocation. - * There are two kinds of reasons: hard and soft reason. - * - * Soft revocation reasons gracefully disable keys or user-ids. - * Softly revoked keys can no longer be used to encrypt data to or to generate signatures. - * Any signature made after a key has been soft revoked is deemed invalid. - * Any signature made before the key has been soft revoked stays valid. - * Soft revoked info can be re-certified at a later point. - * - * Hard revocation reasons on the other hand renders the key or user-id invalid immediately. - * Hard reasons are suitable to use if for example a key got compromised. - * Any signature made before or after a key has been hard revoked is no longer considered valid. - * Hard revoked information can also not be re-certified. - */ - public enum Reason { - /** - * The key or certification is being revoked without a reason. - * This is a HARD revocation reason and cannot be undone. - */ - NO_REASON((byte) 0), - /** - * The key was superseded by another key. - * This is a SOFT revocation reason and can be undone. - */ - KEY_SUPERSEDED((byte) 1), - /** - * The key has potentially been compromised. - * This is a HARD revocation reason and cannot be undone. - */ - KEY_COMPROMISED((byte) 2), - /** - * The key was retired and shall no longer be used. - * This is a SOFT revocation reason can can be undone. - */ - KEY_RETIRED((byte) 3), - /** - * The user-id is no longer valid. - * This is a SOFT revocation reason and can be undone. - */ - USER_ID_NO_LONGER_VALID((byte) 32), - ; - - private static final Map MAP = new ConcurrentHashMap<>(); - static { - for (Reason r : Reason.values()) { - MAP.put(r.reasonCode, r); - } - } - - /** - * Decode a machine-readable reason code. - * - * @param code byte - * @return reason - */ - public static Reason fromCode(byte code) { - Reason reason = MAP.get(code); - if (reason == null) { - throw new IllegalArgumentException("Invalid revocation reason: " + code); - } - return reason; - } - - /** - * Return true if the {@link Reason} the provided code encodes is a hard revocation reason, false - * otherwise. - * Hard revocations cannot be undone, while keys or certifications with soft revocations can be - * re-certified by placing another signature on them. - * - * @param code reason code - * @return is hard - */ - public static boolean isHardRevocation(byte code) { - Reason reason = MAP.get(code); - return reason != KEY_SUPERSEDED && reason != KEY_RETIRED && reason != USER_ID_NO_LONGER_VALID; - } - - /** - * Return true if the given {@link Reason} is a hard revocation, false otherwise. - * Hard revocations cannot be undone, while keys or certifications with soft revocations can be - * re-certified by placing another signature on them. - * - * @param reason reason - * @return is hard - */ - public static boolean isHardRevocation(@Nonnull Reason reason) { - return isHardRevocation(reason.reasonCode); - } - - /** - * Return true if the given {@link Reason} denotes a key revocation. - * @param reason reason - * @return is key revocation - */ - public static boolean isKeyRevocation(@Nonnull Reason reason) { - return isKeyRevocation(reason.code()); - } - - /** - * Return true if the given reason code denotes a key revocation. - * @param code reason code - * @return is key revocation - */ - public static boolean isKeyRevocation(byte code) { - Reason reason = MAP.get(code); - return reason != USER_ID_NO_LONGER_VALID; - } - - private final byte reasonCode; - - Reason(byte reasonCode) { - this.reasonCode = reasonCode; - } - - public byte code() { - return reasonCode; - } - - @Override - public String toString() { - return code() + " - " + name(); - } - } - - public enum RevocationType { - KEY_REVOCATION, - CERT_REVOCATION - } - - private final Reason reason; - private final String description; - - private RevocationAttributes(Reason reason, String description) { - this.reason = reason; - this.description = description; - } - - /** - * Return the machine-readable reason for revocation. - * - * @return reason - */ - public @Nonnull Reason getReason() { - return reason; - } - - /** - * Return the human-readable description for the revocation reason. - * @return description - */ - public @Nonnull String getDescription() { - return description; - } - - /** - * Build a {@link RevocationAttributes} object suitable for key revocations. - * Key revocations are revocations for keys or subkeys. - * - * @return builder - */ - public static WithReason createKeyRevocation() { - return new WithReason(RevocationType.KEY_REVOCATION); - } - - /** - * Build a {@link RevocationAttributes} object suitable for certification (e.g. user-id) revocations. - * - * @return builder - */ - public static WithReason createCertificateRevocation() { - return new WithReason(RevocationType.CERT_REVOCATION); - } - - public static final class WithReason { - - private final RevocationType type; - - private WithReason(RevocationType type) { - this.type = type; - } - - /** - * Set the machine-readable reason. - * Note that depending on whether this is a key-revocation or certification-revocation, - * only certain reason codes are valid. - * Invalid input will result in an {@link IllegalArgumentException} to be thrown. - * - * @param reason reason - * @throws IllegalArgumentException in case of an invalid revocation reason - * @return builder - */ - public WithDescription withReason(Reason reason) { - throwIfReasonTypeMismatch(reason, type); - return new WithDescription(reason); - } - - private void throwIfReasonTypeMismatch(Reason reason, RevocationType type) { - if (type == RevocationType.KEY_REVOCATION) { - if (reason == Reason.USER_ID_NO_LONGER_VALID) { - throw new IllegalArgumentException("Reason " + reason + " can only be used for certificate revocations, not to revoke keys."); - } - } else if (type == RevocationType.CERT_REVOCATION) { - switch (reason) { - case KEY_SUPERSEDED: - case KEY_COMPROMISED: - case KEY_RETIRED: - throw new IllegalArgumentException("Reason " + reason + " can only be used for key revocations, not to revoke certificates."); - } - } - } - - } - - public static final class WithDescription { - - private final Reason reason; - - private WithDescription(Reason reason) { - this.reason = reason; - } - - /** - * Set a human-readable description of the revocation reason. - * - * @param description description - * @return revocation attributes - */ - public RevocationAttributes withDescription(@Nonnull String description) { - return new RevocationAttributes(reason, description); - } - - /** - * Set an empty human-readable description. - * @return revocation attributes - */ - public RevocationAttributes withoutDescription() { - return withDescription(""); - } - } -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/util/RevocationAttributes.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/util/RevocationAttributes.kt new file mode 100644 index 00000000..39f69fbe --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/util/RevocationAttributes.kt @@ -0,0 +1,155 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key.util + +class RevocationAttributes( + val reason: Reason, + val description: String) { + + /** + * Reason for revocation. + * There are two kinds of reasons: hard and soft reason. + * + * Soft revocation reasons gracefully disable keys or user-ids. + * Softly revoked keys can no longer be used to encrypt data to or to generate signatures. + * Any signature made after a key has been soft revoked is deemed invalid. + * Any signature made before the key has been soft revoked stays valid. + * Soft revoked info can be re-certified at a later point. + * + * Hard revocation reasons on the other hand renders the key or user-id invalid immediately. + * Hard reasons are suitable to use if for example a key got compromised. + * Any signature made before or after a key has been hard revoked is no longer considered valid. + * Hard revoked information can also not be re-certified. + */ + enum class Reason(val code: Byte) { + /** + * The key or certification is being revoked without a reason. + * This is a HARD revocation reason and cannot be undone. + */ + NO_REASON(0), + /** + * The key was superseded by another key. + * This is a SOFT revocation reason and can be undone. + */ + KEY_SUPERSEDED(1), + /** + * The key has potentially been compromised. + * This is a HARD revocation reason and cannot be undone. + */ + KEY_COMPROMISED(2), + /** + * The key was retired and shall no longer be used. + * This is a SOFT revocation reason can can be undone. + */ + KEY_RETIRED(3), + /** + * The user-id is no longer valid. + * This is a SOFT revocation reason and can be undone. + */ + USER_ID_NO_LONGER_VALID(32), + ; + + fun code() = code + + override fun toString(): String { + return "$code - $name" + } + + companion object { + + @JvmStatic + private val MAP = values().associateBy { it.code } + + /** + * Decode a machine-readable reason code. + * + * @param code byte + * @return reason + */ + @JvmStatic + fun fromCode(code: Byte) = MAP[code] ?: throw IllegalArgumentException("Invalid revocation reason: $code") + + /** + * Return true if the [Reason] the provided code encodes is a hard revocation reason, false + * otherwise. + * Hard revocations cannot be undone, while keys or certifications with soft revocations can be + * re-certified by placing another signature on them. + * + * @param code reason code + * @return is hard + */ + @JvmStatic + fun isHardRevocation(code: Byte) = MAP[code]?.let { isHardRevocation(it) } ?: true + + /** + * Return true if the given [Reason] is a hard revocation, false otherwise. + * Hard revocations cannot be undone, while keys or certifications with soft revocations can be + * re-certified by placing another signature on them. + * + * @param reason reason + * @return is hard + */ + @JvmStatic + fun isHardRevocation(reason: Reason) = when (reason) { + KEY_SUPERSEDED, KEY_RETIRED, USER_ID_NO_LONGER_VALID -> false + else -> true + } + + /** + * Return true if the given reason code denotes a key revocation. + * @param code reason code + * @return is key revocation + */ + @JvmStatic + fun isKeyRevocation(code: Byte) = MAP[code]?.let { isKeyRevocation(it) } ?: false + + /** + * Return true if the given [Reason] denotes a key revocation. + * @param reason reason + * @return is key revocation + */ + @JvmStatic + fun isKeyRevocation(reason: Reason) = when (reason) { + USER_ID_NO_LONGER_VALID -> false + else -> true + } + } + } + + enum class RevocationType { + KEY_REVOCATION, + CERT_REVOCATION + } + + companion object { + @JvmStatic + fun createKeyRevocation() = WithReason(RevocationType.KEY_REVOCATION) + + @JvmStatic + fun createCertificateRevocation() = WithReason(RevocationType.CERT_REVOCATION) + } + + class WithReason(val type: RevocationType) { + + fun withReason(reason: Reason): WithDescription { + require(reasonTypeMatches(reason, type)) { + "Reason $reason can only be used for ${if (type == RevocationType.KEY_REVOCATION) "certificate" else "key"} revocations." + } + return WithDescription(reason) + } + + private fun reasonTypeMatches(reason: Reason, type: RevocationType): Boolean { + return when (type) { + RevocationType.KEY_REVOCATION -> reason != Reason.USER_ID_NO_LONGER_VALID + RevocationType.CERT_REVOCATION -> reason == Reason.USER_ID_NO_LONGER_VALID || reason == Reason.NO_REASON + } + } + } + + class WithDescription(val reason: Reason) { + fun withDescription(description: String): RevocationAttributes = RevocationAttributes(reason, description) + fun withoutDescription() = RevocationAttributes(reason, "") + } +} \ No newline at end of file